From cc48b73402683af373699f12ca5e1a3ef827cdd1 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Mon, 26 Feb 2024 11:52:52 +0100 Subject: [PATCH 01/10] MAINT: Add deprecation warning for python <= 3.7 --- pyaedt/__init__.py | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/pyaedt/__init__.py b/pyaedt/__init__.py index 5d11363c46d..88ee25d5c7d 100644 --- a/pyaedt/__init__.py +++ b/pyaedt/__init__.py @@ -1,14 +1,50 @@ # -*- coding: utf-8 -*- import os +import sys +import warnings if os.name == "nt": os.environ["PYTHONMALLOC"] = "malloc" -pyaedt_path = os.path.dirname(__file__) +LATEST_DEPRECATED_PYTHON_VERSION = (3, 7) -__version__ = "0.8.dev0" +def deprecation_warning(): + """Warning message informing users that some Python versions are deprecated in PyAEDT.""" + # Store warnings showwarning + existing_showwarning = warnings.showwarning + + # Define and use custom showwarning + def custom_show_warning(message, category, filename, lineno, file=None, line=None): + """Custom warning used to remove :loc: pattern.""" + print(f"{category.__name__}: {message}") + + warnings.showwarning = custom_show_warning + + current_version = sys.version_info[:2] + if current_version <= LATEST_DEPRECATED_PYTHON_VERSION: + str_current_version = "{}.{}".format(*sys.version_info[:2]) + warnings.warn( + "Current python version ({}) is deprecated in PyAEDT. We encourage you " + "to upgrade to the latest version to benefit from the latest features " + "and security updates.".format(str_current_version), + PendingDeprecationWarning, + ) + + # Restore warnings showwarning + warnings.showwarning = existing_showwarning + + +deprecation_warning() + +# + +pyaedt_path = os.path.dirname(__file__) +__version__ = "0.8.dev0" version = __version__ + +# + import pyaedt.downloads as downloads from pyaedt.generic import constants import pyaedt.generic.DataHandlers as data_handler From 0ad4035c36f9d796f365974f3c6afceabd12e025 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Mon, 26 Feb 2024 11:53:25 +0100 Subject: [PATCH 02/10] TESTS: Add test on deprecation warning --- _unittest/test_warnings.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 _unittest/test_warnings.py diff --git a/_unittest/test_warnings.py b/_unittest/test_warnings.py new file mode 100644 index 00000000000..7a2c7b1bec4 --- /dev/null +++ b/_unittest/test_warnings.py @@ -0,0 +1,23 @@ +import sys +from unittest.mock import patch +import warnings + +from pyaedt import LATEST_DEPRECATED_PYTHON_VERSION +from pyaedt import deprecation_warning + + +@patch.object(warnings, "warn") +def test_deprecation_warning(mock_warn): + deprecation_warning() + + current_version = sys.version_info[:2] + if current_version <= LATEST_DEPRECATED_PYTHON_VERSION: + str_current_version = "{}.{}".format(*sys.version_info[:2]) + expected = ( + "Current python version ({}) is deprecated in PyAEDT. We encourage you " + "to upgrade to the latest version to benefit from the latest features " + "and security updates.".format(str_current_version) + ) + mock_warn.assert_called_once_with(expected, PendingDeprecationWarning) + else: + mock_warn.assert_not_called() From ed6f6f8a81e8198978410442c6f7f93c0a8d4dbd Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Mon, 26 Feb 2024 11:57:09 +0100 Subject: [PATCH 03/10] CI: Remove EDB automatic labeling --- .github/labeler.yml | 12 ------------ .github/labels.yml | 4 ---- 2 files changed, 16 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 8e72bd5082c..badf6a1e12b 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -19,18 +19,6 @@ testing: - "_unittest_ironpython/run_unittests.py" - "_unittest_ironpython/run_unittests_batchmode.cmd" -# TODO : Remove once EDB is extracted from PyAEDT -edb: -- changed-files: - - any-glob-to-any-file: - - "examples/00-EDB/*" - - "examples/01-HFSS3DLayout/EDB_in_3DLayout.py" - - "examples/01-HFSS3DLayout/Dcir_in_3DLayout.py" - - "examples/05-Q3D/Q3D_from_EDB.py" - - "examples/05-Q3D/Q3D_DC_IR.py" - - "pyaedt/edb_core/**/*" - - "pyaedt/edb.py" - examples: - changed-files: - any-glob-to-any-file: diff --git a/.github/labels.yml b/.github/labels.yml index 78f0ebc7060..e1d868c2e23 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -30,10 +30,6 @@ description: Anything related to testing color: 5802B8 -- name: edb - description: Anything related to the EDB API - color: C4C25D - - name: examples description: Anything related to the examples color: 3C2E5F From 5f5cff468f851bd9a3ff70752cd89b4dd82a53b2 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Mon, 26 Feb 2024 11:58:03 +0100 Subject: [PATCH 04/10] CI: Remove steps related to EDB documentation --- .github/workflows/full_documentation.yml | 18 ------------------ .github/workflows/nightly-docs.yml | 17 ----------------- 2 files changed, 35 deletions(-) diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index 38364e830ad..e26d1f00622 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -85,13 +85,6 @@ jobs: path: doc/_build/html retention-days: 7 - - name: Upload HTML documentation artifact - uses: actions/upload-artifact@v3 - with: - name: documentation-html-edb - path: doc/_build/html/EDBAPI - retention-days: 7 - # - name: Upload PDF documentation artifact # uses: actions/upload-artifact@v4 # with: @@ -159,14 +152,3 @@ jobs: index-name: pyaedt-v${{ env.VERSION_MEILI }} host-url: ${{ vars.MEILISEARCH_HOST_URL }} api-key: ${{ env.MEILISEARCH_API_KEY }} - pymeilisearchopts: --stop_urls \"EDBAPI\" # Add EDB API as another index. - - - name: "Deploy the stable documentation index for EDB API" - uses: ansys/actions/doc-deploy-index@v4 - with: - cname: ${{ env.DOCUMENTATION_CNAME }}/version/${{ env.VERSION }}/EDBAPI/ - index-name: pyedb-v${{ env.VERSION_MEILI }} - host-url: ${{ vars.MEILISEARCH_HOST_URL }} - api-key: ${{ env.MEILISEARCH_API_KEY }} - doc-artifact-name: documentation-html-edb # Add only EDB API as page in this index. - pymeilisearchopts: --port 8001 #serve in another port diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index bebff44a875..e3ce3a34e56 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -45,12 +45,6 @@ jobs: path: doc/_build/html retention-days: 7 - - name: Upload HTML documentation artifact - uses: actions/upload-artifact@v4 - with: - name: documentation-html-edb - path: doc/_build/html/EDBAPI - retention-days: 7 docs_upload: needs: docs_build @@ -82,17 +76,6 @@ jobs: index-name: pyaedt-vdev host-url: ${{ vars.MEILISEARCH_HOST_URL }} api-key: ${{ env.MEILISEARCH_API_KEY }} - pymeilisearchopts: --stop_urls \"EDBAPI\" # Add EDB API as another index to show it in dropdown button - - - name: "Deploy the dev documentation index for EDB API" - uses: ansys/actions/doc-deploy-index@v4 - with: - cname: ${{ env.DOCUMENTATION_CNAME }}/version/dev/EDBAPI/ - index-name: pyedb-vdev - host-url: ${{ vars.MEILISEARCH_HOST_URL }} - api-key: ${{ env.MEILISEARCH_API_KEY }} - doc-artifact-name: documentation-html-edb # Add only EDB API as page in this index. - pymeilisearchopts: --port 8001 # serve in another port as 8000 is deafult # docstring_testing: # runs-on: Windows From 63b0dcdb80de34086b07a412521e9aa9641da643 Mon Sep 17 00:00:00 2001 From: Sebastien Morais Date: Mon, 26 Feb 2024 13:22:46 +0100 Subject: [PATCH 05/10] FIX: Compatibility with IronPython --- pyaedt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyaedt/__init__.py b/pyaedt/__init__.py index 88ee25d5c7d..a4dc7d0b4fc 100644 --- a/pyaedt/__init__.py +++ b/pyaedt/__init__.py @@ -17,7 +17,7 @@ def deprecation_warning(): # Define and use custom showwarning def custom_show_warning(message, category, filename, lineno, file=None, line=None): """Custom warning used to remove :loc: pattern.""" - print(f"{category.__name__}: {message}") + print("{}: {}".format(category.__name__, message)) warnings.showwarning = custom_show_warning From 8f18524703812ba1bd270f3c4d66acb8461e3ba8 Mon Sep 17 00:00:00 2001 From: SMoraisAnsys <146729917+SMoraisAnsys@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:19:34 +0000 Subject: [PATCH 06/10] CI: Update workflows runs-on (#4285) --- .github/workflows/full_documentation.yml | 2 +- .github/workflows/ironpython.yml | 2 +- .github/workflows/unit_tests.yml | 4 ++-- .github/workflows/unit_tests_solvers.bkp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index e26d1f00622..afb1575f756 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -33,7 +33,7 @@ jobs: full_documentation: # The type of runner that the job will run on name: full_documentation - runs-on: [windows-latest, pyaedt] + runs-on: [Windows, self-hosted, pyaedt] timeout-minutes: 720 strategy: matrix: diff --git a/.github/workflows/ironpython.yml b/.github/workflows/ironpython.yml index b169c44d1c3..c55245a41d7 100644 --- a/.github/workflows/ironpython.yml +++ b/.github/workflows/ironpython.yml @@ -18,7 +18,7 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: [windows-latest, pyaedt] + runs-on: [Windows, self-hosted, pyaedt] # Steps represent a sequence of tasks that will be executed as part of the job steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 2dcee8a270d..3601105a7ad 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -30,7 +30,7 @@ jobs: # This workflow contains a single job called "build" build_solvers: # The type of runner that the job will run on - runs-on: [ windows-latest, pyaedt ] + runs-on: [Windows, self-hosted, pyaedt] strategy: matrix: python-version: [ '3.10' ] @@ -93,7 +93,7 @@ jobs: build: # The type of runner that the job will run on - runs-on: [windows-latest, pyaedt] + runs-on: [Windows, self-hosted, pyaedt] strategy: matrix: python-version: ['3.10'] diff --git a/.github/workflows/unit_tests_solvers.bkp b/.github/workflows/unit_tests_solvers.bkp index 19080841594..4d0691a5dab 100644 --- a/.github/workflows/unit_tests_solvers.bkp +++ b/.github/workflows/unit_tests_solvers.bkp @@ -30,7 +30,7 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: [windows-latest, pyaedt] + runs-on: [Windows, self-hosted, pyaedt] strategy: matrix: python-version: ['3.10'] From 6e6b63d5940754dbab21fee7cf57d1e902603865 Mon Sep 17 00:00:00 2001 From: Samuel Lopez <85613111+Samuelopez-ansys@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:31:36 +0100 Subject: [PATCH 07/10] Add custom LSF (#4290) --- pyaedt/desktop.py | 69 +++++++++++++++++++++----------------- pyaedt/generic/settings.py | 11 ++++++ 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 82096093206..00bcd34a048 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -101,37 +101,46 @@ def launch_desktop_on_port(): def launch_aedt_in_lsf(non_graphical, port): # pragma: no cover """Launch AEDT in LSF in GRPC mode.""" - if settings.lsf_queue: - command = [ - "bsub", - "-n", - str(settings.lsf_num_cores), - "-R", - '"rusage[mem={}]"'.format(settings.lsf_ram), - "-queue {}".format(settings.lsf_queue), - "-Is", - settings.lsf_aedt_command, - "-grpcsrv", - str(port), - ] + if not settings.custom_lsf_command: + if settings.lsf_queue: + command = [ + "bsub", + "-n", + str(settings.lsf_num_cores), + "-R", + '"rusage[mem={}]"'.format(settings.lsf_ram), + "-queue {}".format(settings.lsf_queue), + "-Is", + settings.lsf_aedt_command, + "-grpcsrv", + str(port), + ] + else: + command = [ + "bsub", + "-n", + str(settings.lsf_num_cores), + "-R", + '"rusage[mem={}]"'.format(settings.lsf_ram), + "-Is", + settings.lsf_aedt_command, + "-grpcsrv", + str(port), + ] + if non_graphical: + command.append("-ng") + if settings.wait_for_license: + command.append("-waitforlicense") else: - command = [ - "bsub", - "-n", - str(settings.lsf_num_cores), - "-R", - '"rusage[mem={}]"'.format(settings.lsf_ram), - "-Is", - settings.lsf_aedt_command, - "-grpcsrv", - str(port), - ] - if non_graphical: - command.append("-ng") - if settings.wait_for_license: - command.append("-waitforlicense") + command = settings.custom_lsf_command.split(" ") print(command) - p = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + try: # nosec + p = subprocess.Popen( + " ".join(str(x) for x in command), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + except FileNotFoundError: # nosec + p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + timeout = settings.lsf_timeout i = 0 while i < timeout: @@ -1909,7 +1918,7 @@ def submit_job( elif "\\ $end" == line[:6]: lin = "\\ $end \\'{}\\'\\\n".format(clustername) f1.write(lin) - elif "NumCores" in line: + elif "NumCores=" in line: lin = "\\ \\ \\ \\ NumCores={}\\\n".format(numcores) f1.write(lin) elif "NumNodes=1" in line: diff --git a/pyaedt/generic/settings.py b/pyaedt/generic/settings.py index b8d23d08894..aa8a0ab80fb 100644 --- a/pyaedt/generic/settings.py +++ b/pyaedt/generic/settings.py @@ -48,6 +48,7 @@ def __init__(self): self._lsf_aedt_command = "ansysedt" self._lsf_timeout = 3600 self._lsf_queue = None + self._custom_lsf_command = None self._aedt_environment_variables = { "ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3": "1", "ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE": "1", @@ -178,6 +179,16 @@ def lsf_timeout(self): def lsf_timeout(self, value): self._lsf_timeout = int(value) + @property + def custom_lsf_command(self): + """Command to launch in the LSF Scheduler. The default is ``None``. + This attribute is valid only on Linux systems running LSF Scheduler.""" + return self._custom_lsf_command + + @custom_lsf_command.setter + def custom_lsf_command(self, value): + self._custom_lsf_command = value + @property def aedt_version(self): """AEDT version in the form ``"2023.x"``. In AEDT 2022 R2 and later, From 14d9aad16def055fcadc521344b8e2f6f8876c25 Mon Sep 17 00:00:00 2001 From: gmalinve <103059376+gmalinve@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:49:55 +0100 Subject: [PATCH 08/10] refacto examples (#4221) Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> Co-authored-by: Maxime Rey <87315832+MaxJPRey@users.noreply.github.com> Co-authored-by: IreneWoyna Co-authored-by: Samuel Lopez <85613111+Samuelopez-ansys@users.noreply.github.com> --- _unittest/test_12_1_PostProcessing.py | 2 + _unittest_solvers/test_00_analyze.py | 2 +- examples/03-Maxwell/Maxwell2D_DCConduction.py | 43 +++- .../03-Maxwell/Maxwell2D_Electrostatic.py | 47 +++- ...eaf.py => Maxwell2D_PMSynchronousMotor.py} | 28 +- examples/03-Maxwell/Maxwell2D_Transient.py | 39 +-- examples/03-Maxwell/Maxwell3DTeam7.py | 239 +++++++++--------- examples/03-Maxwell/Maxwell3D_Choke.py | 17 +- examples/03-Maxwell/Maxwell3D_Segmentation.py | 15 +- .../03-Maxwell/Maxwell3D_Team3_bath_plate.py | 78 +++--- examples/03-Maxwell/Maxwell_Magnet.py | 19 +- examples/05-Q3D/Q3D_DC_IR.py | 16 +- examples/05-Q3D/Q3D_Example.py | 8 +- examples/05-Q3D/Q3D_from_EDB.py | 15 +- examples/06-Multiphysics/MRI.py | 14 +- .../Maxwell3D_Icepak_2Way_Coupling.py | 9 +- pyaedt/application/Analysis.py | 4 +- pyaedt/application/Analysis3D.py | 7 +- pyaedt/maxwell.py | 6 +- pyaedt/modeler/cad/Primitives.py | 8 +- pyaedt/modeler/modeler3d.py | 2 +- pyaedt/modules/AdvancedPostProcessing.py | 6 +- pyaedt/modules/Mesh.py | 16 +- pyaedt/modules/PostProcessor.py | 29 +-- 24 files changed, 375 insertions(+), 294 deletions(-) rename examples/03-Maxwell/{Maxwell2D_NissanLeaf.py => Maxwell2D_PMSynchronousMotor.py} (97%) diff --git a/_unittest/test_12_1_PostProcessing.py b/_unittest/test_12_1_PostProcessing.py index e1fc3d22c6a..f885cd37675 100644 --- a/_unittest/test_12_1_PostProcessing.py +++ b/_unittest/test_12_1_PostProcessing.py @@ -89,6 +89,8 @@ def test_01B_Field_Plot(self): assert len(self.aedtapp.setups[0].sweeps[0].frequencies) > 0 assert isinstance(self.aedtapp.setups[0].sweeps[0].basis_frequencies, list) assert len(self.aedtapp.setups[0].sweeps[1].basis_frequencies) == 2 + mesh_file_path = self.aedtapp.post.export_mesh_obj(setup_name, intrinsic) + assert os.path.exists(mesh_file_path) @pytest.mark.skipif(is_linux or sys.version_info < (3, 8), reason="Not running in ironpython") def test_01_Animate_plt(self): diff --git a/_unittest_solvers/test_00_analyze.py b/_unittest_solvers/test_00_analyze.py index 512db792b0e..126eed52f7d 100644 --- a/_unittest_solvers/test_00_analyze.py +++ b/_unittest_solvers/test_00_analyze.py @@ -185,7 +185,7 @@ def test_02_hfss_export_results(self, hfss_app): assert len(exported_files) == 0 hfss_app.analyze_setup(name="test", num_cores=6) exported_files = hfss_app.export_results() - assert len(exported_files) == 3 + assert len(exported_files) == 39 exported_files = hfss_app.export_results( matrix_type="Y", ) diff --git a/examples/03-Maxwell/Maxwell2D_DCConduction.py b/examples/03-Maxwell/Maxwell2D_DCConduction.py index 859c5650191..c31603a61cd 100644 --- a/examples/03-Maxwell/Maxwell2D_DCConduction.py +++ b/examples/03-Maxwell/Maxwell2D_DCConduction.py @@ -47,8 +47,8 @@ # dxf_layers = m2d.get_dxf_layers(DXFPath) # m2d.import_dxf(DXFPath, dxf_layers, scale=1E-05) -ParasolidPath = pyaedt.downloads.download_file("x_t", "Ansys_logo_2D.x_t") -m2d.modeler.import_3d_cad(ParasolidPath) +parasolid_path = pyaedt.downloads.download_file("x_t", "Ansys_logo_2D.x_t") +m2d.modeler.import_3d_cad(parasolid_path) ################################################################################## # Define variables @@ -137,12 +137,25 @@ variations=variations, plotname="Resistance vs. Material", ) + +############################################################################### +# Get solution data +# ~~~~~~~~~~~~~~~~~ +# Get solution data using the object ``report``` to get resistance values +# and plot data outside AEDT. + d = report.get_solution_data() resistence = d.data_magnitude() material_index = d.primary_sweep_values d.primary_sweep = "MaterialIndex" d.plot(snapshot_path=os.path.join(results_folder, "M2D_DCConduction.jpg")) +############################################################################### +# Create material index vs resistance table +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create material index vs resistance table to use in PDF report generator. +# Create ``colors`` table to customize each row of the material index vs resistance table. + material_index_vs_resistance = [["Material", "Resistance"]] colors = [[(255, 255, 255), (0, 255, 0)]] for i in range(len(d.primary_sweep_values)): @@ -208,32 +221,56 @@ # Generate a PDF report with output of simulation. pdf_report = AnsysReport(project_name=m2d.project_name, design_name=m2d.design_name, version="2023.2") + +# Customize text font. + pdf_report.report_specs.font = "times" pdf_report.report_specs.text_font_size = 10 -pdf_report.project_name = m2d.project_name + +# Create report + pdf_report.create() + +# Add project's design info to report. + pdf_report.add_project_info(m2d) + +# Add model picture in a new chapter and add text. + pdf_report.add_chapter("Model Picture") pdf_report.add_text("This section contains the model picture") pdf_report.add_image(model_picture, "Model Picture", width=80, height=60) + +# Add in a new chapter field overlay plots. + pdf_report.add_chapter("Field overlay") pdf_report.add_sub_chapter("Plots") pdf_report.add_text("This section contains the fields overlay.") pdf_report.add_image(os.path.join(results_folder, "mag_E.jpg"), "Mag E", width=120, height=80) pdf_report.add_page_break() + +# Add a new section to display results. + pdf_report.add_section() pdf_report.add_chapter("Results") pdf_report.add_sub_chapter("Resistance vs. Material") pdf_report.add_text("This section contains resistance vs material data.") # Aspect ratio is automatically calculated if only width is provided pdf_report.add_image(os.path.join(results_folder, "M2D_DCConduction.jpg"), width=130) + +# Add a new subchapter to display resistance data from previously created table. + pdf_report.add_sub_chapter("Resistance data table") pdf_report.add_text("This section contains Resistance data.") pdf_report.add_table("Resistance Data", content=material_index_vs_resistance, formatting=colors, col_widths=[75, 100]) + +# Add table of content and save PDF. + pdf_report.add_toc() pdf_report.save_pdf(results_folder, "AEDT_Results.pdf") ################################################################################## # Release desktop # ~~~~~~~~~~~~~~~ + m2d.release_desktop() diff --git a/examples/03-Maxwell/Maxwell2D_Electrostatic.py b/examples/03-Maxwell/Maxwell2D_Electrostatic.py index df21b3763f8..74150bb9e7d 100644 --- a/examples/03-Maxwell/Maxwell2D_Electrostatic.py +++ b/examples/03-Maxwell/Maxwell2D_Electrostatic.py @@ -5,7 +5,6 @@ It shows how to create the geometry, load material properties from an Excel file and set up the mesh settings. Moreover, it focuses on post-processing operations, in particular how to plot field line traces, relevant for an electrostatic analysis. - """ ################################################################################# # Perform required imports @@ -22,10 +21,10 @@ desktopVersion = '2023.2' -sName = 'MySetupAuto' -sType = 'Electrostatic' -dName = 'Design1' -pName = pyaedt.generate_unique_project_name() +setup_name = 'MySetupAuto' +solver = 'Electrostatic' +design_name = 'Design1' +project_name = pyaedt.generate_unique_project_name() non_graphical = False ################################################################################# @@ -62,10 +61,10 @@ # ~~~~~~~~~~~~~~~~~ # Launch Maxwell 2D and save the project. -M2D = pyaedt.Maxwell2d(projectname=pName, +M2D = pyaedt.Maxwell2d(projectname=project_name, specified_version=desktopVersion, - designname=dName, - solution_type=sType, + designname=design_name, + solution_type=solver, new_desktop_session=True, non_graphical=non_graphical ) @@ -140,11 +139,11 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create, update, validate and analyze the setup. -setup = M2D.create_setup(setupname=sName) +setup = M2D.create_setup(setupname=setup_name) setup.props['PercentError'] = 0.5 setup.update() M2D.validate_simple() -M2D.analyze_setup(sName) +M2D.analyze_setup(setup_name) ################################################################################## # Evaluate the E Field tangential component @@ -152,7 +151,7 @@ # Evaluate the E Field tangential component along the given polylines. # Add these operations to the Named Expression list in Field Calculator. -fields = M2D.odesign.GetModule('FieldsReporter') +fields = M2D.ofieldsreporter fields.CalcStack("clear") fields.EnterQty("E") fields.EnterEdge("Poly1") @@ -172,8 +171,8 @@ # the ground, the electrode and the region # and as ``In surface objects`` only the region. -plot = M2D.post.create_fieldplot_line_traces(["Ground", "Electrode", "Region"], - "Region", +plot = M2D.post.create_fieldplot_line_traces(seeding_faces=["Ground", "Electrode", "Region"], + in_volume_tracing_objs="Region", plot_name="LineTracesTest") ################################################################################### @@ -187,6 +186,28 @@ plot.LineWidth = 3 plot.update() +################################################################################### +# Export field line traces plot +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Export field line traces plot. +# For field lint traces plot, the export file format is ``.fldplt``. + +M2D.post.export_field_plot(plotname="LineTracesTest", filepath=M2D.toolkit_directory, file_format="fldplt") + +########################################################## +# Export a field plot to an image file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Export the flux lines plot to an image file using PyVista Python package. + +M2D.post.plot_field_from_fieldplot(plot.name, show=False) + +########################################################## +# Export the mesh field plot +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Export the mesh in ``aedtplt`` format. + +M2D.post.export_mesh_obj(setup_name=M2D.nominal_adaptive) + ################################################################################### # Save project and close AEDT # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/03-Maxwell/Maxwell2D_NissanLeaf.py b/examples/03-Maxwell/Maxwell2D_PMSynchronousMotor.py similarity index 97% rename from examples/03-Maxwell/Maxwell2D_NissanLeaf.py rename to examples/03-Maxwell/Maxwell2D_PMSynchronousMotor.py index 96500ab1226..dee5a6bc1b3 100644 --- a/examples/03-Maxwell/Maxwell2D_NissanLeaf.py +++ b/examples/03-Maxwell/Maxwell2D_PMSynchronousMotor.py @@ -11,7 +11,7 @@ # Perform required imports. from math import sqrt as mysqrt -from pyaedt import generate_unique_folder_name + import csv import os import pyaedt @@ -24,17 +24,11 @@ desktopVersion = "2023.2" -sName = "MySetupAuto" -sType = "TransientXY" - -pName = pyaedt.generate_unique_project_name() -dName = "Sinusoidal" +setup_name = "MySetupAuto" +solver = "TransientXY" -################################################################################# -# Initialize dictionaries -# ~~~~~~~~~~~~~~~~~~~~~~~ -# Initialize dictionaries that contain all the definitions for the design -# variables and output variables. +project_name = pyaedt.generate_unique_project_name() +design_name = "Sinusoidal" ################################################################################# # Initialize definitions for stator, rotor, and shaft @@ -124,10 +118,10 @@ # ~~~~~~~~~~~~~~~~~ # Launch Maxwell 2D and save the project. -M2D = pyaedt.Maxwell2d(projectname=pName, +M2D = pyaedt.Maxwell2d(projectname=project_name, specified_version=desktopVersion, - designname=dName, - solution_type=sType, + designname=design_name, + solution_type=solver, new_desktop_session=True, non_graphical=non_graphical ) @@ -558,7 +552,7 @@ def create_cs_magnets(pm_id, cs_name, point_direction): # Turn on core loss. core_loss_list = ['Rotor', 'Stator'] -M2D.set_core_losses(core_loss_list, value=True) +M2D.set_core_losses(core_loss_list) ########################################################## # Compute transient inductance @@ -586,7 +580,7 @@ def create_cs_magnets(pm_id, cs_name, point_direction): # ~~~~~~~~~~~~~~~~~~~~~~~~~ # Create the setup and validate it. -setup = M2D.create_setup(setupname=sName) +setup = M2D.create_setup(setupname=setup_name) setup.props["StopTime"] = "StopTime" setup.props["TimeStep"] = "TimeStep" setup.props["SaveFieldsType"] = "None" @@ -715,7 +709,7 @@ def create_cs_magnets(pm_id, cs_name, point_direction): # Analyze and save the project. M2D.save_project() -M2D.analyze_setup(sName, use_auto_settings=False) +M2D.analyze_setup(setup_name, use_auto_settings=False) ########################################################## # Create flux lines plot on region diff --git a/examples/03-Maxwell/Maxwell2D_Transient.py b/examples/03-Maxwell/Maxwell2D_Transient.py index c65c9b89ab8..1c1bfed4a76 100644 --- a/examples/03-Maxwell/Maxwell2D_Transient.py +++ b/examples/03-Maxwell/Maxwell2D_Transient.py @@ -25,6 +25,14 @@ import os import pyaedt +import tempfile + +########################################################################################### +# Create temporary directory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create temporary directory. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") ############################################################################### # Set non-graphical mode @@ -71,7 +79,7 @@ # ~~~~~~~~~~ # Plot the model. -maxwell_2d.plot(show=False, export_path=os.path.join(maxwell_2d.working_directory, "Image.jpg"), plot_air_objects=True) +maxwell_2d.plot(show=False, export_path=os.path.join(temp_dir.name, "Image.jpg"), plot_air_objects=True) ############################################################################### # Create setup @@ -113,29 +121,29 @@ timesteps = [str(i * 2e-4) + "s" for i in range(11)] id_list = [f.id for f in face_lists] -animatedGif = maxwell_2d.post.plot_animated_field( - "Mag_B", - id_list, - "Surface", +gif = maxwell_2d.post.plot_animated_field( + quantity="Mag_B", + object_list=id_list, + plot_type="Surface", intrinsics={"Time": "0s"}, variation_variable="Time", variation_list=timesteps, show=False, export_gif=False, ) -animatedGif.isometric_view = False -animatedGif.camera_position = [15, 15, 80] -animatedGif.focal_point = [15, 15, 0] -animatedGif.roll_angle = 0 -animatedGif.elevation_angle = 0 -animatedGif.azimuth_angle = 0 +gif.isometric_view = False +gif.camera_position = [15, 15, 80] +gif.focal_point = [15, 15, 0] +gif.roll_angle = 0 +gif.elevation_angle = 0 +gif.azimuth_angle = 0 # Set off_screen to False to visualize the animation. -# animatedGif.off_screen = False -animatedGif.animate() +# gif.off_screen = False +gif.animate() ############################################################################### -# Generate plot outside of AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Generate plot outside AEDT +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ # Generate the same plot outside AEDT. solutions = maxwell_2d.post.get_solution_data("InputCurrent(PHA)", primary_sweep_variable="Time") @@ -147,3 +155,4 @@ # Close AEDT. maxwell_2d.release_desktop() +temp_dir.cleanup() diff --git a/examples/03-Maxwell/Maxwell3DTeam7.py b/examples/03-Maxwell/Maxwell3DTeam7.py index 8ffd1d989b6..9e15f8074ee 100644 --- a/examples/03-Maxwell/Maxwell3DTeam7.py +++ b/examples/03-Maxwell/Maxwell3DTeam7.py @@ -3,17 +3,26 @@ ----------------------------------------- This example uses PyAEDT to set up the TEAM 7 problem for an asymmetric conductor with a hole and solve it using the Maxwell 3D Eddy Current solver. +https://www.compumag.org/wp/wp-content/uploads/2018/06/problem7.pdf """ ########################################################################################### # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. -from pyaedt import Maxwell3d -from pyaedt import generate_unique_project_name import numpy as np -import csv import os +import tempfile + +from pyaedt import Maxwell3d +from pyaedt.generic.general_methods import write_csv + +########################################################################################### +# Create temporary directory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create temporary directory. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") ########################################################################################### # Set non-graphical mode @@ -27,24 +36,22 @@ # Launch AEDT and Maxwell 3D # ~~~~~~~~~~~~~~~~~~~~~~~~~~ # Launch AEDT and Maxwell 3D. The following code sets up the project and design names, the solver, and -# the version. It also creates an instance of the ``Maxwell3d`` class named ``M3D``. - -Project_Name = "COMPUMAG" -Design_Name = "TEAM 7 Asymmetric Conductor" -Solver = "EddyCurrent" -DesktopVersion = "2023.2" - -M3D = Maxwell3d( - projectname=generate_unique_project_name(), - designname=Design_Name, - solution_type=Solver, - specified_version=DesktopVersion, +# the version. It also creates an instance of the ``Maxwell3d`` class named ``m3d``. + +project_name = "COMPUMAG" +design_name = "TEAM 7 Asymmetric Conductor" +solver = "EddyCurrent" +desktop_version = "2023.2" + +m3d = Maxwell3d( + projectname=project_name, + designname=design_name, + solution_type=solver, + specified_version=desktop_version, non_graphical=non_graphical, new_desktop_session=True ) -M3D.modeler.model_units = "mm" -modeler = M3D.modeler -Plot = M3D.odesign.GetModule("ReportSetup") +m3d.modeler.model_units = "mm" ########################################################################################### # Add Maxwell 3D setup @@ -57,12 +64,13 @@ dc_freq = 0.1 stop_freq = 50 -Setup = M3D.create_setup(setupname="Setup1") -Setup.props["Frequency"] = "200Hz" -Setup.props["HasSweepSetup"] = True -Setup.add_eddy_current_sweep("LinearStep", dc_freq, stop_freq, stop_freq - dc_freq, clear=True) -Setup.props["UseHighOrderShapeFunc"] = True -Setup.props["PercentError"] = 0.4 +setup = m3d.create_setup(setupname="Setup1") +setup.props["Frequency"] = "200Hz" +setup.props["HasSweepSetup"] = True +setup.add_eddy_current_sweep("LinearStep", dc_freq, stop_freq, stop_freq - dc_freq, clear=True) +setup.props["UseHighOrderShapeFunc"] = True +setup.props["PercentError"] = 0.4 +setup.update() ########################################################################################### # Define coil dimensions @@ -95,14 +103,14 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create a coordinate system for positioning the coil. -M3D.modeler.create_coordinate_system(origin=coil_centre, mode="view", view="XY", name="Coil_CS") +m3d.modeler.create_coordinate_system(origin=coil_centre, mode="view", view="XY", name="Coil_CS") ########################################################################################### # Create polyline # ~~~~~~~~~~~~~~~ # Create a polyline. One quarter of the coil is modeled by sweeping a 2D sheet along a polyline. -test = M3D.modeler.create_polyline(position_list=[P1, P2, P3, P4], segment_type=["Line", "Arc"], name="Coil") +test = m3d.modeler.create_polyline(position_list=[P1, P2, P3, P4], segment_type=["Line", "Arc"], name="Coil") test.set_crosssection_properties(type="Rectangle", width=coil_thk, height=coil_height) ########################################################################################### @@ -110,12 +118,12 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Duplicate and unit the polyline to create a full coil. -M3D.modeler.duplicate_around_axis( +m3d.modeler.duplicate_around_axis( "Coil", cs_axis="Global", angle=90, nclones=4, create_new_objects=True, is_3d_comp=False ) -M3D.modeler.unite("Coil,Coil_1,Coil_2") -M3D.modeler.unite("Coil,Coil_3") -M3D.modeler.fit_all() +m3d.modeler.unite("Coil, Coil_1, Coil_2") +m3d.modeler.unite("Coil, Coil_3") +m3d.modeler.fit_all() ########################################################################################### # Assign material and if solution is allowed inside coil @@ -123,33 +131,33 @@ # Assign the material ``Cooper`` from the Maxwell internal library to the coil and # allow a solution inside the coil. -M3D.assign_material("Coil", "Copper") -M3D.solve_inside("Coil") +m3d.assign_material("Coil", "Copper") +m3d.solve_inside("Coil") ########################################################################################### # Create terminal # ~~~~~~~~~~~~~~~ -# Create a terminal for the coil from a cross section that is split and one half deleted. +# Create a terminal for the coil from a cross-section that is split and one half deleted. -M3D.modeler.section("Coil", "YZ") -M3D.modeler.separate_bodies("Coil_Section1") -M3D.modeler.delete("Coil_Section1_Separate1") +m3d.modeler.section("Coil", "YZ") +m3d.modeler.separate_bodies("Coil_Section1") +m3d.modeler.delete("Coil_Section1_Separate1") # Add variable for coil excitation # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Add a design variable for coil excitation. The NB units here are AmpereTurns. Coil_Excitation = 2742 -M3D["Coil_Excitation"] = str(Coil_Excitation) + "A" -M3D.assign_current("Coil_Section1", amplitude="Coil_Excitation", solid=False) -M3D.modeler.set_working_coordinate_system("Global") +m3d["Coil_Excitation"] = str(Coil_Excitation) + "A" +m3d.assign_current(object_list="Coil_Section1", amplitude="Coil_Excitation", solid=False) +m3d.modeler.set_working_coordinate_system("Global") ########################################################################################### # Add a material # ~~~~~~~~~~~~~~ # Add a material named ``team3_aluminium``. -mat = M3D.materials.add_material("team7_aluminium") +mat = m3d.materials.add_material("team7_aluminium") mat.conductivity = 3.526e7 ########################################################################################### @@ -157,18 +165,18 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Model the aluminium plate with a hole by subtracting two rectangular cuboids. -plate = M3D.modeler.create_box(position=[0, 0, 0], dimensions_list=[294, 294, 19], name="Plate", +plate = m3d.modeler.create_box(position=[0, 0, 0], dimensions_list=[294, 294, 19], name="Plate", matname="team7_aluminium") -M3D.modeler.fit_all() -hole = M3D.modeler.create_box(position=[18, 18, 0], dimensions_list=[108, 108, 19], name="Hole") -M3D.modeler.subtract("Plate", ["Hole"], keep_originals=False) +m3d.modeler.fit_all() +m3d.modeler.create_box(position=[18, 18, 0], dimensions_list=[108, 108, 19], name="Hole") +m3d.modeler.subtract(blank_list="Plate", tool_list=["Hole"], keep_originals=False) ########################################################################################### # Draw a background region # ~~~~~~~~~~~~~~~~~~~~~~~~ # Draw a background region that uses the default properties for an air region. -M3D.modeler.create_air_region(x_pos=100, y_pos=100, z_pos=100, x_neg=100, y_neg=100, z_neg=100) +m3d.modeler.create_air_region(x_pos=100, y_pos=100, z_pos=100, x_neg=100, y_neg=100, z_neg=100) ################################################################################ # Adjust eddy effects for plate and coil @@ -177,8 +185,8 @@ # for all parts. The setting for eddy effect is ignored for the stranded conductor type # used in the coil. -M3D.eddy_effects_on(object_list="Plate") -M3D.eddy_effects_on(object_list=["Coil", "Region", "Line_A1_B1mesh", "Line_A2_B2mesh"], +m3d.eddy_effects_on(object_list="Plate") +m3d.eddy_effects_on(object_list=["Coil", "Region", "Line_A1_B1mesh", "Line_A2_B2mesh"], activate_eddy_effects=False, activate_displacement_current=False) @@ -187,7 +195,8 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create an expression for the Z component of B in Gauss using the fields calculator. -Fields = M3D.odesign.GetModule("FieldsReporter") +Fields = m3d.ofieldsreporter +Fields.CalcStack("clear") Fields.EnterQty("B") Fields.CalcOp("ScalarZ") Fields.EnterScalarFunc("Phase") @@ -207,28 +216,27 @@ mesh_diameter = "2mm" line_points_1 = [["0mm", "72mm", "34mm"], ["288mm", "72mm", "34mm"]] -polyline = modeler.create_polyline(position_list=line_points_1, name=lines[0]) -L1Mesh = modeler.create_polyline(position_list=line_points_1, name=lines[0] + "mesh") -L1Mesh.set_crosssection_properties(type="Circle", width=mesh_diameter) +polyline = m3d.modeler.create_polyline(position_list=line_points_1, name=lines[0]) +l1_mesh = m3d.modeler.create_polyline(position_list=line_points_1, name=lines[0] + "mesh") +l1_mesh.set_crosssection_properties(type="Circle", width=mesh_diameter) line_points_2 = [["0mm", "144mm", "34mm"], ["288mm", "144mm", "34mm"]] -polyline2 = modeler.create_polyline(position_list=line_points_2, name=lines[1]) -polyline2_mesh = modeler.create_polyline(position_list=line_points_2, name=lines[1] + "mesh") -polyline2_mesh.set_crosssection_properties(type="Circle", width=mesh_diameter) +polyline2 = m3d.modeler.create_polyline(position_list=line_points_2, name=lines[1]) +l2_mesh = m3d.modeler.create_polyline(position_list=line_points_2, name=lines[1] + "mesh") +l2_mesh.set_crosssection_properties(type="Circle", width=mesh_diameter) ############################################################################### # Plot model # ~~~~~~~~~~ # Plot the model. -M3D.plot(show=False, export_path=os.path.join(M3D.working_directory, "Image.jpg"), plot_air_objects=False) +m3d.plot(show=False, export_path=os.path.join(temp_dir.name, "model.jpg"), plot_air_objects=False) ################################################################################ # Published measurement results are included with this script via the list below. # Test results are used to create text files for import into a rectangular plot # and to overlay simulation results. -project_dir = M3D.working_directory dataset = [ "Bz A1_B1 000 0", "Bz A1_B1 050 0", @@ -345,74 +353,62 @@ [-1.35, -0.71, -0.81, -0.67, 0.15, 1.39, 2.67, 3.00, 4.01, 3.80, 4.00, 3.02, 2.20, 2.78, 1.58, 1.37, 0.93], ] +################################################################################ +# Write dataset values in a CSV file +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Dataset details are used to encode test parameters in the text files. -# For example, ``Bz A1_B1 050 0`` is the Z component of flux density ``B`` -# along line ``A1_B1`` at 50 Hz and 0 deg. These text files are created -# and saved in the default project directory. - -print("project_dir", project_dir) -dataset_range = range(int(0), len(dataset), int(1)) -line_length_range = range(int(0), len(line_length), int(1)) -dataset_list = [] - -for item in dataset_range: - dataset_list.clear() - for jtem in line_length_range: - dataset_list.insert(jtem, data[item][jtem]) - ziplist = zip(line_length, dataset_list) - with open(project_dir + "\\" + str(dataset[item]) + ".csv", "w", newline="") as f: - writer = csv.writer(f, delimiter=",") - writer.writerow(header) - writer.writerows(ziplist) - -# Create rectangular plots -# ~~~~~~~~~~~~~~~~~~~~~~~~ -# Create rectangular plots, using text file encoding to control their formatting. Create -# the DC plot separately because it needs a different frequency and phase than the other plots. +# For example, ``Bz A1_B1 050 0`` is the Z component of flux density ``B``. +# along line ``A1_B1`` at 50 Hz and 0 deg. -for item in dataset_range: +line_length.insert(0, header[0]) +for i in range(len(dataset)): + data[i].insert(0, header[1]) + ziplist = zip(line_length, data[i]) + file_path = os.path.join(temp_dir.name, str(dataset[i]) + ".csv") + write_csv(output=file_path, list_data=ziplist) + +################################################################################ +# Create rectangular plots and import test data into report +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create rectangular plots, using text file encoding to control their formatting. +# Import test data into correct plot and overlay with simulation results. +# Variations for a DC plot must have different frequency and phase than the other plots. + +for item in range(len(dataset)): if item % 2 == 0: - plotname = dataset[item][0:3] + "Along the Line" + dataset[item][2:9] + ", " + dataset[item][9:12] + "Hz" - if dataset[item][9:12] == "000": + t = dataset[item] + plot_name = t[0:3] + "Along the Line" + t[2:9] + ", " + t[9:12] + "Hz" + if t[9:12] == "000": variations = { "Distance": ["All"], "Freq": [str(dc_freq) + "Hz"], "Phase": ["0deg"], "Coil_Excitation": ["All"], } - M3D.post.create_report( - plotname=plotname, - report_category="Fields", - context="Line_" + dataset[item][3:8], - primary_sweep_variable="Distance", - variations=variations, - expressions=dataset[item][0:2], - ) else: variations = { "Distance": ["All"], - "Freq": [dataset[item][9:12] + "Hz"], + "Freq": [t[9:12] + "Hz"], "Phase": ["0deg", "90deg"], "Coil_Excitation": ["All"], } - M3D.post.create_report( - plotname=plotname, - report_category="Fields", - context="Line_" + dataset[item][3:8], - primary_sweep_variable="Distance", - variations=variations, - expressions=dataset[item][0:2], - ) - -# Import test data into correct plot and overlay with simulation results -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Import test data into the correct plot and overlay it with the simulation results. - -if item == 0: - Plot.ImportIntoReport(plotname, os.path.join(project_dir, str(dataset[item]) + ".csv")) -else: - Plot.ImportIntoReport(plotname, project_dir + "\\" + str(dataset[item - 1]) + ".csv") - Plot.ImportIntoReport(plotname, project_dir + "\\" + str(dataset[item]) + ".csv") + report = m3d.post.create_report( + plotname=plot_name, + report_category="Fields", + context="Line_" + t[3:8], + primary_sweep_variable="Distance", + variations=variations, + expressions=t[0:2], + ) + file_path = os.path.join(temp_dir.name, str(dataset[i]) + ".csv") + report.import_traces(file_path, plot_name) + +################################################################################################### +# Analyze project +# ~~~~~~~~~~~~~~~ +# Analyze the project. + +m3d.analyze() ################################################################################################### # Create plots of induced current and flux density on surface of plate @@ -420,23 +416,16 @@ # Create two plots of the induced current (``Mag_J``) and the flux density (``Mag_B``) on the # surface of the plate. -surflist = modeler.get_object_faces("Plate") +surf_list = m3d.modeler.get_object_faces("Plate") intrinsic_dict = {"Freq": "200Hz", "Phase": "0deg"} -M3D.post.create_fieldplot_surface(surflist, "Mag_J", intrinsincDict=intrinsic_dict, plot_name="Mag_J") -M3D.post.create_fieldplot_surface(surflist, "Mag_B", intrinsincDict=intrinsic_dict, plot_name="Mag_B") - -################################################################################################### -# Save project and solve -# ~~~~~~~~~~~~~~~~~~~~~~ -# Save the project and solve it. - -M3D.save_project() -M3D.analyze() +m3d.post.create_fieldplot_surface(surf_list, "Mag_J", intrinsincDict=intrinsic_dict, plot_name="Mag_J") +m3d.post.create_fieldplot_surface(surf_list, "Mag_B", intrinsincDict=intrinsic_dict, plot_name="Mag_B") +m3d.post.create_fieldplot_surface(surf_list, "Mesh", intrinsincDict=intrinsic_dict, plot_name="Mesh") #################################################################################################### -# Release AEDT from PyAEDT scripting -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Release AEDT from PyAEDT scripting. If you wanted to leave AEDT and the project open -# after running the above script, in the following command, you would set ``(False, False)``. +# Release AEDT and clean up temporary directory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Release AEDT and remove both the project and temporary directories. -M3D.release_desktop(True, True) +m3d.release_desktop(True, True) +temp_dir.cleanup() diff --git a/examples/03-Maxwell/Maxwell3D_Choke.py b/examples/03-Maxwell/Maxwell3D_Choke.py index d21690b8d1e..ba3d1a6a58d 100644 --- a/examples/03-Maxwell/Maxwell3D_Choke.py +++ b/examples/03-Maxwell/Maxwell3D_Choke.py @@ -11,6 +11,14 @@ import json import os import pyaedt +import tempfile + +########################################################################################### +# Create temporary directory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create temporary directory. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") ############################################################################### # Set non-graphical mode @@ -104,7 +112,7 @@ # Covert a dictionary to a JSON file. PyAEDT methods ask for the path of the # JSON file as an argument. You can convert a dictionary to a JSON file. -json_path = os.path.join(m3d.working_directory, "choke_example.json") +json_path = os.path.join(temp_dir.name, "choke_example.json") with open(json_path, "w") as outfile: json.dump(values, outfile) @@ -163,8 +171,8 @@ mesh = m3d.mesh mesh.assign_skin_depth( - [first_winding_list[0], second_winding_list[0], third_winding_list[0]], - 0.20, + names=[first_winding_list[0], second_winding_list[0], third_winding_list[0]], + skindepth=0.20, triangulation_max_length="10mm", meshop_name="skin_depth", ) @@ -203,7 +211,7 @@ m3d.save_project() m3d.modeler.fit_all() -m3d.plot(show=False, export_path=os.path.join(m3d.working_directory, "Image.jpg"), plot_air_objects=True) +m3d.plot(show=False, export_path=os.path.join(temp_dir.name, "Image.jpg"), plot_air_objects=True) ############################################################################### # Close AEDT @@ -213,3 +221,4 @@ # All methods provide for saving the project before closing. m3d.release_desktop() +temp_dir.cleanup() diff --git a/examples/03-Maxwell/Maxwell3D_Segmentation.py b/examples/03-Maxwell/Maxwell3D_Segmentation.py index e012e6b4e18..5837867e158 100644 --- a/examples/03-Maxwell/Maxwell3D_Segmentation.py +++ b/examples/03-Maxwell/Maxwell3D_Segmentation.py @@ -10,9 +10,17 @@ # Perform required imports. from pyaedt import downloads -from pyaedt import generate_unique_folder_name from pyaedt import Maxwell3d +import tempfile + +########################################################################################### +# Create temporary directory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create temporary directory. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") + ############################################################################### # Set non-graphical mode # ~~~~~~~~~~~~~~~~~~~~~~ @@ -25,8 +33,8 @@ # Download .aedt file example # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Set local temporary folder to export the .aedt file to. -temp_folder = generate_unique_folder_name() -aedt_file = downloads.download_file("object_segmentation", "Motor3D_obj_segments.aedt", temp_folder) + +aedt_file = downloads.download_file("object_segmentation", "Motor3D_obj_segments.aedt", temp_dir.name) ################################################################################## # Launch Maxwell 3D @@ -98,3 +106,4 @@ m3d.save_project() m3d.release_desktop() +temp_dir.cleanup() diff --git a/examples/03-Maxwell/Maxwell3D_Team3_bath_plate.py b/examples/03-Maxwell/Maxwell3D_Team3_bath_plate.py index d93025e6f78..703299d9c39 100644 --- a/examples/03-Maxwell/Maxwell3D_Team3_bath_plate.py +++ b/examples/03-Maxwell/Maxwell3D_Team3_bath_plate.py @@ -3,6 +3,7 @@ ------------------------------- This example uses PyAEDT to set up the TEAM 3 bath plate problem and solve it using the Maxwell 3D Eddy Current solver. +https://www.compumag.org/wp/wp-content/uploads/2018/06/problem3.pdf """ ################################################################################## # Perform required imports @@ -11,6 +12,14 @@ import os import pyaedt +import tempfile + +########################################################################################### +# Create temporary directory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create temporary directory. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") ################################################################################## # Set non-graphical mode @@ -29,19 +38,18 @@ project_name = "COMPUMAG" design_name = "TEAM 3 Bath Plate" -Solver = "EddyCurrent" +solver = "EddyCurrent" desktop_version = "2023.2" m3d = pyaedt.Maxwell3d( - projectname=pyaedt.generate_unique_project_name(), + projectname=project_name, designname=design_name, - solution_type=Solver, + solution_type=solver, specified_version=desktop_version, non_graphical=non_graphical, new_desktop_session=True, ) -uom = m3d.modeler.model_units = "mm" -modeler = m3d.modeler +m3d.modeler.model_units = "mm" ############################################################################### # Add variable @@ -50,7 +58,7 @@ # position of the coil. Coil_Position = -20 -m3d["Coil_Position"] = str(Coil_Position) + uom # Creates a design variable in Maxwell +m3d["Coil_Position"] = str(Coil_Position) + m3d.modeler.model_units ################################################################################ # Add material @@ -110,27 +118,27 @@ # density. The following code also adds a small diameter cylinder to refine the # mesh locally around the line. -Line_Points = [["0mm", "-55mm", "0.5mm"], ["0mm", "55mm", "0.5mm"]] -P1 = modeler.create_polyline(position_list=Line_Points, name="Line_AB") -P2 = modeler.create_polyline(position_list=Line_Points, name="Line_AB_MeshRefinement") -P2.set_crosssection_properties(type="Circle", width="0.5mm") +line_points = [["0mm", "-55mm", "0.5mm"], ["0mm", "55mm", "0.5mm"]] +m3d.modeler.create_polyline(position_list=line_points, name="Line_AB") +poly = m3d.modeler.create_polyline(position_list=line_points, name="Line_AB_MeshRefinement") +poly.set_crosssection_properties(type="Circle", width="0.5mm") ############################################################################### # Plot model # ~~~~~~~~~~ # Plot the model. -m3d.plot(show=False, export_path=os.path.join(m3d.working_directory, "Image.jpg"), plot_air_objects=False) +m3d.plot(show=False, export_path=os.path.join(temp_dir.name, "Image.jpg"), plot_air_objects=False) ############################################################################### # Add Maxwell 3D setup # ~~~~~~~~~~~~~~~~~~~~ # Add a Maxwell 3D setup with frequency points at 50 Hz and 200 Hz. -Setup = m3d.create_setup(setupname="Setup1") -Setup.props["Frequency"] = "200Hz" -Setup.props["HasSweepSetup"] = True -Setup.add_eddy_current_sweep(range_type="LinearStep", start=50, end=200, count=150, clear=True) +setup = m3d.create_setup(setupname="Setup1") +setup.props["Frequency"] = "200Hz" +setup.props["HasSweepSetup"] = True +setup.add_eddy_current_sweep(range_type="LinearStep", start=50, end=200, count=150, clear=True) ################################################################################ # Adjust eddy effects @@ -138,32 +146,33 @@ # Adjust eddy effects for the ladder plate and the search coil. The setting for # eddy effect is ignored for the stranded conductor type used in the search coil. -m3d.eddy_effects_on(["LadderPlate"], activate_eddy_effects=True, activate_displacement_current=True) -m3d.eddy_effects_on(["SearchCoil"], activate_eddy_effects=False, activate_displacement_current=True) +m3d.eddy_effects_on(object_list=["LadderPlate"], activate_eddy_effects=True, activate_displacement_current=True) +m3d.eddy_effects_on(object_list=["SearchCoil"], activate_eddy_effects=False, activate_displacement_current=True) ################################################################################ # Add linear parametric sweep # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Add a linear parametric sweep for the two coil positions. -sweepname = "CoilSweep" -param = m3d.parametrics.add("Coil_Position", -20, 0, 20, "LinearStep", parametricname=sweepname) +sweep_name = "CoilSweep" +param = m3d.parametrics.add("Coil_Position", -20, 0, 20, "LinearStep", parametricname=sweep_name) param["SaveFields"] = True param["CopyMesh"] = False param["SolveWithCopiedMeshOnly"] = True +################################################################################ # Solve parametric sweep # ~~~~~~~~~~~~~~~~~~~~~~ # Solve the parametric sweep directly so that results of all variations are available. -m3d.analyze_setup(sweepname) +m3d.analyze_setup(sweep_name) ############################################################################### # Create expression for Bz # ~~~~~~~~~~~~~~~~~~~~~~~~ # Create an expression for Bz using the fields calculator. -Fields = m3d.odesign.GetModule("FieldsReporter") +Fields = m3d.ofieldsreporter Fields.EnterQty("B") Fields.CalcOp("ScalarZ") Fields.EnterScalar(1000) @@ -176,17 +185,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Plot mag(Bz) as a function of frequency for both coil positions. -variations = {"Distance": ["All"], "Freq": ["All"], "Phase": ["0deg"], "Coil_Position": ["-20mm"]} -m3d.post.create_report( - expressions="mag(Bz)", - report_category="Fields", - context="Line_AB", - variations=variations, - primary_sweep_variable="Distance", - plotname="mag(Bz) Along 'Line_AB' Offset Coil", -) - -variations = {"Distance": ["All"], "Freq": ["All"], "Phase": ["0deg"], "Coil_Position": ["0mm"]} +variations = {"Distance": ["All"], "Freq": ["All"], "Phase": ["0deg"], "Coil_Position": ["All"]} m3d.post.create_report( expressions="mag(Bz)", report_category="Fields", @@ -197,11 +196,9 @@ ) ############################################################################### -# Generate plot outside of AEDT -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Generate the same plot outside AEDT. - -variations = {"Distance": ["All"], "Freq": ["All"], "Phase": ["0deg"], "Coil_Position": ["All"]} +# Get simulation results from a solved setup +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get simulation results from a solved setup as a ``SolutionData`` object. solutions = m3d.post.get_solution_data( expressions="mag(Bz)", @@ -232,13 +229,14 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Plot the induced current density, ``"Mag_J"``, on the surface of the ladder plate. -surflist = modeler.get_object_faces("LadderPlate") +ladder_plate = m3d.modeler.objects_by_name["LadderPlate"] intrinsic_dict = {"Freq": "50Hz", "Phase": "0deg"} -m3d.post.create_fieldplot_surface(surflist, "Mag_J", intrinsincDict=intrinsic_dict, plot_name="Mag_J") +m3d.post.create_fieldplot_surface(ladder_plate.faces, "Mag_J", intrinsincDict=intrinsic_dict, plot_name="Mag_J") ############################################################################### # Release AEDT # ~~~~~~~~~~~~ # Release AEDT from the script engine, leaving both AEDT and the project open. -m3d.release_desktop(True, True) +m3d.release_desktop(False, False) +temp_dir.cleanup() diff --git a/examples/03-Maxwell/Maxwell_Magnet.py b/examples/03-Maxwell/Maxwell_Magnet.py index 5a5abc53db4..a31cafafef1 100644 --- a/examples/03-Maxwell/Maxwell_Magnet.py +++ b/examples/03-Maxwell/Maxwell_Magnet.py @@ -12,6 +12,14 @@ from pyaedt import Maxwell3d from pyaedt import generate_unique_project_name import os +import tempfile + +########################################################################################### +# Create temporary directory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create temporary directory. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") ############################################################################### # Set non-graphical mode @@ -24,7 +32,7 @@ ############################################################################### # Launch AEDT # ~~~~~~~~~~~ -# Launch AEDT 2023 R2 in graphical mode. +# Launch AEDT in graphical mode. m3d = Maxwell3d(projectname=generate_unique_project_name(), specified_version="2023.2", @@ -58,7 +66,7 @@ # ~~~~~~~~~~ # Plot the model. -m3d.plot(show=False, export_path=os.path.join(m3d.working_directory, "Image.jpg"), plot_air_objects=True) +m3d.plot(show=False, export_path=os.path.join(temp_dir.name, "Image.jpg"), plot_air_objects=True) ############################################################################### # Solve setup @@ -91,9 +99,9 @@ # ~~~~~~~~~~~~~~~ # Get mass center using the fields calculator. -xval = m3d.post.get_scalar_field_value("CM_X", None) -yval = m3d.post.get_scalar_field_value("CM_Y", None) -zval = m3d.post.get_scalar_field_value("CM_Z", None) +xval = m3d.post.get_scalar_field_value("CM_X") +yval = m3d.post.get_scalar_field_value("CM_Y") +zval = m3d.post.get_scalar_field_value("CM_Z") ############################################################################### # Create variables @@ -120,3 +128,4 @@ m3d.save_project() m3d.release_desktop(close_projects=True, close_desktop=True) +temp_dir.cleanup() diff --git a/examples/05-Q3D/Q3D_DC_IR.py b/examples/05-Q3D/Q3D_DC_IR.py index 976279b53c4..ef404249cc1 100644 --- a/examples/05-Q3D/Q3D_DC_IR.py +++ b/examples/05-Q3D/Q3D_DC_IR.py @@ -4,19 +4,19 @@ This example shows how you can use PyAEDT to create a design in Q3D Extractor and run a DC IR Drop simulation starting from an EDB Project. """ - ############################################################################### # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. + import os import pyaedt - ############################################################################### # Set up project files and path # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Download needed project file and set up temporary project directory. + project_dir = pyaedt.generate_unique_folder_name() aedb_project = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb', destination=project_dir) coil = pyaedt.downloads.download_file('inductance_3d_component', 'air_coil.a3dcomp') @@ -25,12 +25,12 @@ output_edb = os.path.join(project_dir, project_name + '.aedb') output_q3d = os.path.join(project_dir, project_name + '_q3d.aedt') - ############################################################################### # Open EDB # ~~~~~~~~ # Open the EDB project and create a cutout on the selected nets # before exporting to Q3D. + edb = pyaedt.Edb(aedb_project, edbversion="2023.2") edb.cutout(["1.2V_AVDLL_PLL", "1.2V_AVDDL", "1.2V_DVDDL", "NetR106_1"], ["GND"], @@ -38,7 +38,6 @@ use_pyaedt_extent_computing=True, ) - ############################################################################### # Identify pin positions # ~~~~~~~~~~~~~~~~~~~~~~ @@ -99,8 +98,6 @@ setup.export_to_q3d(output_q3d, keep_net_name=True) h3d.close_project() - - ############################################################################### # Open Q3D # ~~~~~~~~ @@ -110,7 +107,6 @@ q3d.modeler.delete("GND") q3d.delete_all_nets() - ############################################################################### # Insert inductors # ~~~~~~~~~~~~~~~~ @@ -131,7 +127,7 @@ q3d.modeler.set_working_coordinate_system("Global") q3d.modeler.create_coordinate_system(location_r106_1, name="R106") -comp3= q3d.modeler.insert_3d_component(res, targetCS="R106",geo_params={'$Resistance': 2000}) +comp3 = q3d.modeler.insert_3d_component(res, targetCS="R106",geo_params={'$Resistance': 2000}) comp3.rotate(q3d.AXIS.Z, -90) q3d.modeler.set_working_coordinate_system("Global") @@ -157,6 +153,7 @@ # Use previously calculated positions to identify faces, # select the net "1_Top" and # assign sources and sinks on nets. + sink_f = q3d.modeler.create_circle(q3d.PLANE.XY, location_u11_scl, 0.1) source_f1 = q3d.modeler.create_circle(q3d.PLANE.XY, location_u9_1_scl, 0.1) source_f2 = q3d.modeler.create_circle(q3d.PLANE.XY, location_u9_2_scl, 0.1) @@ -224,7 +221,6 @@ log_scale=False, ) - ############################################################################### # Computing Voltage on Source Circles # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -253,11 +249,11 @@ for curve in curves: print(data.data_real(curve)) - ############################################################################### # Close AEDT # ~~~~~~~~~~ # After the simulation completes, you can close AEDT or release it using the # ``release_desktop`` method. All methods provide for saving projects before closing. + q3d.save_project() q3d.release_desktop() diff --git a/examples/05-Q3D/Q3D_Example.py b/examples/05-Q3D/Q3D_Example.py index 2bf882c57f0..7bf28f603bc 100644 --- a/examples/05-Q3D/Q3D_Example.py +++ b/examples/05-Q3D/Q3D_Example.py @@ -34,7 +34,8 @@ ############################################################################### # Launch AEDT and Q3D Extractor # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Launch AEDT 2023 R2 in graphical mode and launch Q3D Extractor. This example uses SI units. +# Launch AEDT 2023 R2 in graphical mode and launch Q3D Extractor. +# This example uses SI units. q = pyaedt.Q3d(projectname=pyaedt.generate_unique_project_name(), specified_version="2023.2", @@ -132,8 +133,6 @@ data_plot_self = q.matrices[0].get_sources_for_plot(get_self_terms=True, get_mutual_terms=False) data_plot_mutual = q.get_traces_for_plot(get_self_terms=False, get_mutual_terms=True, category="C") -data_plot_self -data_plot_mutual ############################################################################### # Create rectangular plot @@ -157,8 +156,6 @@ # Get the report data into a data structure that allows you to manipulate it. a = q.post.get_solution_data(expressions=data_plot_self, context="Original") -a.intrinsics["Freq"] -a.data_magnitude() a.plot() ############################################################################### @@ -166,6 +163,7 @@ # ~~~~~~~~~~ # After the simulation completes, you can close AEDT or release it using the # ``release_desktop`` method. All methods provide for saving projects before closing. + pyaedt.settings.enable_debug_logger = False pyaedt.settings.enable_debug_methods_argument_logger = False q.release_desktop(close_projects=True, close_desktop=True) diff --git a/examples/05-Q3D/Q3D_from_EDB.py b/examples/05-Q3D/Q3D_from_EDB.py index a104684b832..f9fc72b8e0a 100644 --- a/examples/05-Q3D/Q3D_from_EDB.py +++ b/examples/05-Q3D/Q3D_from_EDB.py @@ -9,32 +9,32 @@ # Perform required imports # ~~~~~~~~~~~~~~~~~~~~~~~~ # Perform required imports. + import os import pyaedt - ############################################################################### # Setup project files and path # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Download of needed project file and setup of temporary project directory. -project_dir = pyaedt.generate_unique_folder_name() + +project_dir = pyaedt.generate_unique_folder_name() aedb_project = pyaedt.downloads.download_file('edb/ANSYS-HSD_V1.aedb',destination=project_dir) project_name = pyaedt.generate_unique_name("HSD") output_edb = os.path.join(project_dir, project_name + '.aedb') output_q3d = os.path.join(project_dir, project_name + '_q3d.aedt') - ############################################################################### # Open EDB # ~~~~~~~~ # Open the edb project and created a cutout on the selected nets # before exporting to Q3D. + edb = pyaedt.Edb(aedb_project, edbversion="2023.2") edb.cutout(["CLOCK_I2C_SCL", "CLOCK_I2C_SDA"], ["GND"], output_aedb_path=output_edb, use_pyaedt_extent_computing=True, ) - ############################################################################### # Identify pins position # ~~~~~~~~~~~~~~~~~~~~~~ @@ -46,7 +46,6 @@ pin_u13_sda = [i for i in edb.components["U13"].pins.values() if i.net_name == "CLOCK_I2C_SDA"] pin_u1_sda = [i for i in edb.components["U1"].pins.values() if i.net_name == "CLOCK_I2C_SDA"] - ############################################################################### # Append Z Positions # ~~~~~~~~~~~~~~~~~~ @@ -79,12 +78,11 @@ # ~~~~~~~~~~~~~ # Create a dummy setup and export the layout in Q3D. # keep_net_name will reassign Q3D nets names from Hfss 3D Layout. + setup = h3d.create_setup() setup.export_to_q3d(output_q3d, keep_net_name=True) h3d.close_project() - - ############################################################################### # Open Q3D # ~~~~~~~~ @@ -100,8 +98,6 @@ # Use previously calculated position to identify faces and # assign sources and sinks on nets. - - f1 = q3d.modeler.get_faceid_from_position(location_u13_scl, obj_name="CLOCK_I2C_SCL") q3d.source(f1, net_name="CLOCK_I2C_SCL") f1 = q3d.modeler.get_faceid_from_position(location_u13_sda, obj_name="CLOCK_I2C_SDA") @@ -147,4 +143,5 @@ # ~~~~~~~~~~ # After the simulation completes, you can close AEDT or release it using the # ``release_desktop`` method. All methods provide for saving projects before closing. + q3d.release_desktop() diff --git a/examples/06-Multiphysics/MRI.py b/examples/06-Multiphysics/MRI.py index 47b94a38bcf..dda7240d152 100644 --- a/examples/06-Multiphysics/MRI.py +++ b/examples/06-Multiphysics/MRI.py @@ -38,9 +38,11 @@ # Phantom consists of two objects: phantom and implant_box # Separate objects are used to selectively assign mesh operations # Material properties defined in this project already contain #electrical and thermal properties. + project_path = downloads.download_file(directory="mri") hfss = Hfss(os.path.join(project_path, "background_SAR.aedt"), specified_version="2023.2", non_graphical=non_graphical, new_desktop_session=True) + ############################################################################### # Insert 3D component # ~~~~~~~~~~~~~~~~~~~ @@ -70,7 +72,6 @@ conv_criteria=2.5, use_cache_for_freq=False) hfss.setups[0].props["MaximumPasses"] = 2 -im_traces ############################################################################### # Edit Sources @@ -111,6 +112,7 @@ plot_type="CutPlane", show_legend=False, filter_objects=["implant_box"], + show=False ) ############################################################################### @@ -121,7 +123,6 @@ # To determine required input, calculate # input_scale = 1/AverageSAR at Point1 - sol_data = hfss.post.get_solution_data(expressions="Average_SAR", primary_sweep_variable="Freq", context="Point1", @@ -206,14 +207,13 @@ # Plot Temperature on cut plane. # Plot Temperature on point. - mech.post.create_fieldplot_cutplane("implant:YZ", "Temperature", filter_objects=["implant_box"], intrinsincDict={"Time": "10s"}) mech.save_project() data = mech.post.get_solution_data("Temperature", primary_sweep_variable="Time", context="Point1", report_category="Fields") -data.plot() +#data.plot() mech.post.plot_animated_field(quantity="Temperature", object_list="implant:YZ", @@ -222,6 +222,7 @@ variation_variable="Time", variation_list=["10s", "20s", "30s", "40s", "50s", "60s"], filter_objects=["implant_box"], + show=False ) ############################################################################### @@ -272,7 +273,6 @@ # ~~~~~~~~~~~ # Create a new mesh region and change accuracy level to 4. - bound = ipk.modeler["implant_box"].bounding_box mesh_box = ipk.modeler.create_box(bound[:3], [bound[3] - bound[0], bound[4] - bound[1], bound[5] - bound[2]]) mesh_box.model = False @@ -303,6 +303,6 @@ ipk.save_project() data = ipk.post.get_solution_data("Point1.Temperature", primary_sweep_variable="Time", report_category="Monitor") -data.plot() +#data.plot() -ipk.release_desktop(False) +ipk.release_desktop(True, True) diff --git a/examples/06-Multiphysics/Maxwell3D_Icepak_2Way_Coupling.py b/examples/06-Multiphysics/Maxwell3D_Icepak_2Way_Coupling.py index 3daac20c2c8..d296c56e2a5 100644 --- a/examples/06-Multiphysics/Maxwell3D_Icepak_2Way_Coupling.py +++ b/examples/06-Multiphysics/Maxwell3D_Icepak_2Way_Coupling.py @@ -72,7 +72,7 @@ # Assign materials: Assign Coil to AWG40 copper, core to ferrite, and region to vacuum. no_strands = 24 -strand_diameter = 0.08 # mm +strand_diameter = 0.08 cu_litz = m3d.materials.duplicate_material("copper", "copper_litz") cu_litz.stacking_type = "Litz Wire" @@ -162,7 +162,8 @@ # Analytical calculation of the DC resistance of the coil cu_cond = float(cu_litz.conductivity.value) -l_conductor = no_turns*2*0.125*3.1415 # average radius of a coil turn = 0.125m +# average radius of a coil turn = 0.125m +l_conductor = no_turns*2*0.125*3.1415 # R = resistivity * length / area / no_strand r_analytical_DC = (1.0 / cu_cond) * l_conductor / (3.1415 * (strand_diameter / 1000 / 2) ** 2) / no_strands @@ -255,7 +256,7 @@ ) velocity_cutplane = ipk.post.create_fieldplot_cutplane( - objlist="Global:XZ", + objlist=["Global:XZ"], quantityName="Velocity Vectors", plot_name="Velocity Vectors" ) @@ -290,4 +291,4 @@ # Release desktop # ~~~~~~~~~~~~~~~ -ipk.release_desktop() \ No newline at end of file +ipk.release_desktop(True, True) diff --git a/pyaedt/application/Analysis.py b/pyaedt/application/Analysis.py index 8a0aac7d8fc..ca29778d2bd 100644 --- a/pyaedt/application/Analysis.py +++ b/pyaedt/application/Analysis.py @@ -1638,7 +1638,7 @@ def analyze( @pyaedt_function_handler() def analyze_setup( self, - name, + name=None, num_cores=4, num_tasks=1, num_gpu=0, @@ -1653,7 +1653,7 @@ def analyze_setup( Parameters ---------- - name : str + name : str, optional Name of the setup, which can be an optimetric setup or a simple setup. If ``None`` all setups will be solved. num_cores : int, optional diff --git a/pyaedt/application/Analysis3D.py b/pyaedt/application/Analysis3D.py index 48ab8586985..cf8027d9d98 100644 --- a/pyaedt/application/Analysis3D.py +++ b/pyaedt/application/Analysis3D.py @@ -325,7 +325,12 @@ def get_components3d_vars(self, component3dname): for line in _all_lines: if "VariableProp(" in line: line_list = line.split("'") - vars[line_list[1]] = line_list[len(line_list) - 2] + if not [ + c for c in line_list[len(line_list) - 2] if c in ["+", "-", "*", "'" "," "/", "(", ")"] + ]: + self[line_list[1]] = line_list[len(line_list) - 2] + else: + vars[line_list[1]] = line_list[len(line_list) - 2] aedt_fh.close() return vars else: diff --git a/pyaedt/maxwell.py b/pyaedt/maxwell.py index 625d3e12535..1ed386e981e 100644 --- a/pyaedt/maxwell.py +++ b/pyaedt/maxwell.py @@ -507,8 +507,8 @@ def eddy_effects_on(self, object_list, activate_eddy_effects=True, activate_disp Parameters ---------- - object_list : list - List of objects. + object_list : list, str + List of objects to assign eddy effects to. activate_eddy_effects : bool, optional Whether to activate eddy effects. The default is ``True``. activate_displacement_current : bool, optional @@ -652,7 +652,7 @@ def assign_current(self, object_list, amplitude=1, phase="0deg", solid=True, swa Parameters ---------- - object_list : list + object_list : list, str List of objects to assign the current source to. amplitude : float or str, optional Current amplitude. The default is ``1A``. diff --git a/pyaedt/modeler/cad/Primitives.py b/pyaedt/modeler/cad/Primitives.py index cccc64e32a6..39f6d6f3723 100644 --- a/pyaedt/modeler/cad/Primitives.py +++ b/pyaedt/modeler/cad/Primitives.py @@ -3036,7 +3036,7 @@ def separate_bodies(self, object_list, create_group=False): Parameters ---------- - object_list : list + object_list : list, str List of objects to separate. create_group : bool, optional Whether to create a group. The default is ``False``. @@ -3117,7 +3117,7 @@ def subtract(self, blank_list, tool_list, keep_originals=True, **kwargs): blank_list : str, Object3d, int or List of str, int and Object3d. List of objects to subtract from. The list can be of either :class:`pyaedt.modeler.Object3d.Object3d` objects or object IDs. - tool_list : list + tool_list : list, str List of objects to subtract. The list can be of either Object3d objects or object IDs. keep_originals : bool, optional @@ -3325,8 +3325,8 @@ def unite(self, unite_list, purge=False, keep_originals=False): Parameters ---------- - unite_list : list - List of objects. + unite_list : list, str + List of objects to unite. purge : bool, optional Purge history after unite. Default is False. keep_originals : bool, optional diff --git a/pyaedt/modeler/modeler3d.py b/pyaedt/modeler/modeler3d.py index 51998af0d7e..5b232fae9ce 100644 --- a/pyaedt/modeler/modeler3d.py +++ b/pyaedt/modeler/modeler3d.py @@ -1301,7 +1301,7 @@ def objects_segmentation( Parameters ---------- - objects_list : list + objects_list : list, str List of objects to apply the segmentation to. It can either be a list of strings (object names), integers (object IDs), or a list of :class:`pyaedt.modeler.cad.object3d.Object3d` classes. diff --git a/pyaedt/modules/AdvancedPostProcessing.py b/pyaedt/modules/AdvancedPostProcessing.py index ab7d1a0c2e6..415ded147d4 100644 --- a/pyaedt/modules/AdvancedPostProcessing.py +++ b/pyaedt/modules/AdvancedPostProcessing.py @@ -391,7 +391,11 @@ def plot_field_from_fieldplot( else: self.ofieldsreporter.UpdateQuantityFieldsPlots(plot_folder) - file_to_add = self.export_field_plot(plotname, self._app.working_directory, file_format="case") + if self.field_plots[plotname].field_type == "DC R/L Fields": + file_format = "fldplt" + else: + file_format = "case" + file_to_add = self.export_field_plot(plotname, self._app.working_directory, file_format=file_format) model = self.get_model_plotter_geometries( generate_mesh=False, get_objects_from_aedt=plot_cad_objs, diff --git a/pyaedt/modules/Mesh.py b/pyaedt/modules/Mesh.py index d13f441eef0..93b9d0c5d5e 100644 --- a/pyaedt/modules/Mesh.py +++ b/pyaedt/modules/Mesh.py @@ -922,7 +922,7 @@ def assign_length_mesh(self, names, isinside=True, maxlength=1, maxel=1000, mesh Parameters ---------- - names : list + names : list, str List of object names or face IDs. isinside : bool, optional Whether the length mesh is inside the selection. The default is ``True``. @@ -1003,7 +1003,13 @@ def assign_length_mesh(self, names, isinside=True, maxlength=1, maxel=1000, mesh @pyaedt_function_handler() def assign_skin_depth( - self, names, skindepth, maxelements=None, triangulation_max_length="0.1mm", numlayers="2", meshop_name=None + self, + names, + skindepth="0.2mm", + maxelements=None, + triangulation_max_length="0.1mm", + numlayers="2", + meshop_name=None, ): """Assign a skin depth for the mesh refinement. @@ -1011,8 +1017,10 @@ def assign_skin_depth( ---------- names : list List of the object names or face IDs. - skindepth : bool - Whether the length mesh is inside the selection. The default is ``True``. + skindepth : str, float, optional + Skin depth value. + It can be either provided as a float or as a string. + The default is ``"0.2mm"``. maxelements : int, optional Maximum number of elements. The default is ``None``, which means this parameter is disabled. triangulation_max_length : str, optional diff --git a/pyaedt/modules/PostProcessor.py b/pyaedt/modules/PostProcessor.py index 030ee475012..84505b7a5f8 100644 --- a/pyaedt/modules/PostProcessor.py +++ b/pyaedt/modules/PostProcessor.py @@ -2785,34 +2785,29 @@ def export_field_plot(self, plotname, filepath, filename="", file_format="aedtpl Name of the file. The default is ``""``. file_format : str, optional - Name of the file extension. The default is ``"aedtplt"``. Option is ``"case"``. + Name of the file extension. The default is ``"aedtplt"``. Options are ``"case"`` and ``"fldplt"``. Returns ------- str - File Path when succeeded. + File path when successful. References ---------- - >>> oModule.ExportFieldPlot """ if not filename: filename = plotname - file_path = os.path.join(filepath, filename + "." + file_format) - if ".case" in file_path: - try: - self.ofieldsreporter.ExportFieldPlot(plotname, False, file_path) - except: # pragma: no cover - self.logger.warning("case file is not supported for this plot. Switching to aedtplt") - file_path = os.path.join(filepath, filename + ".aedtplt") - self.ofieldsreporter.ExportFieldPlot(plotname, False, file_path) - else: # pragma: no cover - self.ofieldsreporter.ExportFieldPlot(plotname, False, file_path) - if settings.remote_rpc_session_temp_folder: - local_path = os.path.join(settings.remote_rpc_session_temp_folder, filename + "." + file_format) - file_path = check_and_download_file(local_path, file_path) - return file_path + filepath = os.path.join(filepath, filename + "." + file_format) + try: + self.ofieldsreporter.ExportFieldPlot(plotname, False, filepath) + if settings.remote_rpc_session_temp_folder: # pragma: no cover + local_path = os.path.join(settings.remote_rpc_session_temp_folder, filename + "." + file_format) + filepath = check_and_download_file(local_path, filepath) + return filepath + except: # pragma: no cover + self.logger.error("{} file format is not supported for this plot.".format(file_format)) + return False @pyaedt_function_handler() def change_field_plot_scale(self, plot_name, minimum_value, maximum_value, is_log=False, is_db=False): From abe2c5d2702bb101aede3bb3b8e69e0a129c2c51 Mon Sep 17 00:00:00 2001 From: Samuel Lopez <85613111+Samuelopez-ansys@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:25:37 +0100 Subject: [PATCH 09/10] Feat/issue 4053 (#4056) Co-authored-by: skrishna Co-authored-by: Sivasubramani Krishnaswamy <76733563+siva-krishnaswamy@users.noreply.github.com> Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- .../T08/cylinder_geometry_creation.csv | 5 + ...inder_geometry_creation_missing_values.csv | 5 + .../cylinder_geometry_creation_wrong_keys.csv | 5 + .../example_models/T08/primitives_file.json | 113 ++++ .../T08/prism_geometry_creation.csv | 5 + ...prism_geometry_creation_missing_values.csv | 5 + .../prism_geometry_creation_wrong_keys.csv | 5 + _unittest/test_08_Primitives3D.py | 132 +++- doc/source/Resources/primitive_example.json | 114 ++++ .../pyaedt_file_data/primitives.rst | 14 + pyaedt/generic/configurations.py | 5 +- pyaedt/modeler/cad/Primitives.py | 575 ++++++++++++++++++ 12 files changed, 980 insertions(+), 3 deletions(-) create mode 100644 _unittest/example_models/T08/cylinder_geometry_creation.csv create mode 100644 _unittest/example_models/T08/cylinder_geometry_creation_missing_values.csv create mode 100644 _unittest/example_models/T08/cylinder_geometry_creation_wrong_keys.csv create mode 100644 _unittest/example_models/T08/primitives_file.json create mode 100644 _unittest/example_models/T08/prism_geometry_creation.csv create mode 100644 _unittest/example_models/T08/prism_geometry_creation_missing_values.csv create mode 100644 _unittest/example_models/T08/prism_geometry_creation_wrong_keys.csv create mode 100644 doc/source/Resources/primitive_example.json diff --git a/_unittest/example_models/T08/cylinder_geometry_creation.csv b/_unittest/example_models/T08/cylinder_geometry_creation.csv new file mode 100644 index 00000000000..d97753eb6cc --- /dev/null +++ b/_unittest/example_models/T08/cylinder_geometry_creation.csv @@ -0,0 +1,5 @@ +# Blocks Cylinder,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,, +name,xc,yc,zc,plane,radius,iradius,height +Cylinder,0,0,0,2,40,0,70 +Cylinder_with_hole,10,10,0,2,40,20,70 diff --git a/_unittest/example_models/T08/cylinder_geometry_creation_missing_values.csv b/_unittest/example_models/T08/cylinder_geometry_creation_missing_values.csv new file mode 100644 index 00000000000..2e79f1412c1 --- /dev/null +++ b/_unittest/example_models/T08/cylinder_geometry_creation_missing_values.csv @@ -0,0 +1,5 @@ +# Blocks Cylinder,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,, +name,xc,yc,zc,plane,radius,iradius,height +Cylinder,0,0,0,2,40,0,70 +Cylinder_with_hole,10,10,0,2,40 diff --git a/_unittest/example_models/T08/cylinder_geometry_creation_wrong_keys.csv b/_unittest/example_models/T08/cylinder_geometry_creation_wrong_keys.csv new file mode 100644 index 00000000000..6ea39eae66f --- /dev/null +++ b/_unittest/example_models/T08/cylinder_geometry_creation_wrong_keys.csv @@ -0,0 +1,5 @@ +# Blocks Cylinder,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,, +name,xc,yc,zc,plane,outerradius,innerradius,height +Cylinder,0,0,0,2,40,0,70 +Cylinder_with_hole,10,10,0,2,40,20,70 diff --git a/_unittest/example_models/T08/primitives_file.json b/_unittest/example_models/T08/primitives_file.json new file mode 100644 index 00000000000..5095face8ba --- /dev/null +++ b/_unittest/example_models/T08/primitives_file.json @@ -0,0 +1,113 @@ +{ + "Primitives": + [ + { + "Primitive Type": "Cylinder", + "Name": "Small", + "Plane": 1, + "Height": 10.0, + "Radius": "3.0mm", + "Internal Radius": 1.0, + "Number of Segments": 0 + }, + { + "Primitive Type": "Cylinder", + "Name": "Big", + "Plane": 0, + "Height": 50.0, + "Radius": 10.0, + "Internal Radius": 0 + }, + { + "Primitive Type": "Cylinder", + "Name": "Big_hole", + "Plane": 0, + "Height": 50.0, + "Radius": 10.0, + "Internal Radius": 5 + }, + { + "Primitive Type": "Box", + "Name": "SmallBox", + "X Length": 10.0, + "Y Length": 10.0, + "Z Length": 10.0 + }, + { + "Primitive Type": "Box", + "Name": "BigBox", + "X Length": 100.0, + "Y Length": 100.0, + "Z Length": 100.0 + } + + ], + "Instances": + [ + { + "Name":"Small", + "Coordinate System": "CS1", + "Origin": ["0mm","1.052dm","2"] + }, + { + "Name":"Small", + "Coordinate System": "Global", + "Origin": ["20um",10,"2.2"] + }, + { + "Name":"Big", + "Coordinate System": "CS2", + "Origin": ["2.0mm",0,50.5] + }, + { + "Name":"Big", + "Coordinate System": "Global", + "Origin": [10,10,10] + }, + { + "Name":"Big_hole", + "CS": "CS1", + "Origin": [0,0,50] + }, + { + "Name":"SmallBox", + "Coordinate System": "CS1", + "Origin": ["0mm","1.052dm","2"] + }, + { + "Name":"SmallBox", + "Coordinate System": "Global", + "Origin": ["20um",10,"2.2"] + }, + { + "Name":"BigBox", + "Coordinate System": "CS2", + "Origin": ["2.0mm",0,50.5] + }, + { + "Name":"BigBox", + "Coordinate System": "Global", + "Origin": [10,10,10] + } + ], + "Coordinate Systems": + [ + { + "Name":"CS1", + "Reference CS": "Global", + "Mode": "Axis/Position", + "Origin": ["20um",10,"2.2"], + "X Axis": [1,0,0], + "Y Point": [0,1,0] + }, + { + "Name": "CS2", + "Mode": "Euler Angle ZYZ", + "Origin": ["1.5",0,"0"], + "Phi": "0deg", + "Theta": 2, + "Psi": 0.5 + } + ], + "Units": "mm" +} \ No newline at end of file diff --git a/_unittest/example_models/T08/prism_geometry_creation.csv b/_unittest/example_models/T08/prism_geometry_creation.csv new file mode 100644 index 00000000000..2676eb53c9e --- /dev/null +++ b/_unittest/example_models/T08/prism_geometry_creation.csv @@ -0,0 +1,5 @@ +# Blocks Prism,,,,,,,,,,,,,,,,,,, +#,,,,,,,,,,,,,,,,,,, +name,xs,ys,zs,xd,yd,zd +block_1,0.65,0.4,0.4,0.2,0.2,0.2 +block_2,0.90,0.4,0.4,0.2,0.2,0.2 diff --git a/_unittest/example_models/T08/prism_geometry_creation_missing_values.csv b/_unittest/example_models/T08/prism_geometry_creation_missing_values.csv new file mode 100644 index 00000000000..1ef9b7ba672 --- /dev/null +++ b/_unittest/example_models/T08/prism_geometry_creation_missing_values.csv @@ -0,0 +1,5 @@ +# Blocks Prism +# +name,xs,ys,zs,xe,ye,ze,xd,yd,zd +block_1,0.65,0.4,0.4,0.85,0.6,0.6,0.2,0.2 +block_2,0.9,0.4,0.4,1.1,0.6,0.6,0.2,0.2,0.2 diff --git a/_unittest/example_models/T08/prism_geometry_creation_wrong_keys.csv b/_unittest/example_models/T08/prism_geometry_creation_wrong_keys.csv new file mode 100644 index 00000000000..0e6210ffaa7 --- /dev/null +++ b/_unittest/example_models/T08/prism_geometry_creation_wrong_keys.csv @@ -0,0 +1,5 @@ +# Blocks Prism +# +name,xs,ys,zs,xe,ye,ze,xl,yl,zl +block_1,0.65,0.4,0.4,0.85,0.6,0.6,0.2,0.2,0.2 +block_2,0.9,0.4,0.4,1.1,0.6,0.6,0.2,0.2,0.2 diff --git a/_unittest/test_08_Primitives3D.py b/_unittest/test_08_Primitives3D.py index 361ab9e2924..5a803019c78 100644 --- a/_unittest/test_08_Primitives3D.py +++ b/_unittest/test_08_Primitives3D.py @@ -6,6 +6,8 @@ from _unittest.conftest import local_path import pytest +from pyaedt import Icepak +from pyaedt import Q2d from pyaedt import generate_unique_name from pyaedt.generic.constants import AXIS from pyaedt.modeler.cad.Primitives import PolylineSegment @@ -21,6 +23,14 @@ component3d = "new.a3dcomp" encrypted_cyl = "encrypted_cylinder.a3dcomp" layout_comp = "Layoutcomponent_231.aedbcomp" +primitive_json_file = "primitives_file.json" +cylinder_primitive_csv_file = "cylinder_geometry_creation.csv" +cylinder_primitive_csv_file_missing_values = "cylinder_geometry_creation_missing_values.csv" +cylinder_primitive_csv_file_wrong_keys = "cylinder_geometry_creation_wrong_keys.csv" +prism_primitive_csv_file = "prism_geometry_creation.csv" +prism_primitive_csv_file_missing_values = "prism_geometry_creation_missing_values.csv" +prism_primitive_csv_file_wrong_keys = "prism_geometry_creation_wrong_keys.csv" + test_subfolder = "T08" if config["desktopVersion"] > "2022.2": assembly = "assembly_231" @@ -238,11 +248,20 @@ def test_05_center_and_centroid(self): tol = 1e-9 assert GeometryOperators.v_norm(o.faces[0].center_from_aedt) - GeometryOperators.v_norm(o.faces[0].center) < tol + def test_06_position(self): + udp = self.aedtapp.modeler.Position(0, 0, 0) + with pytest.raises(IndexError) as execinfo: + item = udp[3] + assert str(execinfo) == "Position index not correct." + assert self.aedtapp.modeler.Position([0]) + + def test_07_sweep_options(self): + assert self.aedtapp.modeler.SweepOptions() + def test_11a_get_object_name_from_edge(self): o = self.create_copper_box() edge = o.edges[0].id assert self.aedtapp.modeler.get_object_name_from_edge_id(edge) == o.name - udp = self.aedtapp.modeler.Position(0, 0, 0) dimensions = [10, 10, 5] o = self.aedtapp.modeler.create_box(udp, dimensions) @@ -1620,6 +1639,7 @@ def test_79_3dcomponent_operations(self): assert obj_3dcomp.mesh_assembly obj_3dcomp.name = "Dipole_pyaedt" assert "Dipole_pyaedt" in self.aedtapp.modeler.user_defined_component_names + assert self.aedtapp.modeler["Dipole_pyaedt"] assert obj_3dcomp.name == "Dipole_pyaedt" if config["desktopVersion"] < "2023.1": assert obj_3dcomp.parameters["dipole_length"] == "l_dipole" @@ -1841,3 +1861,113 @@ def test_87_set_mesh_fusion_settings(self): volume_padding=None, priority=None, ) + + def test_88_import_primitives_file_json(self): + self.aedtapp.insert_design("PrimitiveFromFile") + primitive_file = os.path.join(local_path, "example_models", test_subfolder, primitive_json_file) + primitive_names = self.aedtapp.modeler.import_primitives_from_file(input_file=primitive_file) + assert len(primitive_names) == 9 + + def test_89_import_cylinder_primitives_csv(self): + self.aedtapp.insert_design("PrimitiveFromFile") + primitive_file = os.path.join(local_path, "example_models", test_subfolder, cylinder_primitive_csv_file) + primitive_names = self.aedtapp.modeler.import_primitives_from_file(input_file=primitive_file) + assert len(primitive_names) == 2 + self.aedtapp.insert_design("PrimitiveFromFileTest") + primitive_file = os.path.join( + local_path, "example_models", test_subfolder, cylinder_primitive_csv_file_wrong_keys + ) + with pytest.raises(ValueError): + self.aedtapp.modeler.import_primitives_from_file(input_file=primitive_file) + primitive_file = os.path.join( + local_path, "example_models", test_subfolder, cylinder_primitive_csv_file_missing_values + ) + with pytest.raises(ValueError): + self.aedtapp.modeler.import_primitives_from_file(input_file=primitive_file) + + def test_90_import_prism_primitives_csv(self): + self.aedtapp.insert_design("PrimitiveFromFile") + primitive_file = os.path.join(local_path, "example_models", test_subfolder, prism_primitive_csv_file) + primitive_names = self.aedtapp.modeler.import_primitives_from_file(input_file=primitive_file) + assert len(primitive_names) == 2 + self.aedtapp.insert_design("PrimitiveFromFileTest") + primitive_file = os.path.join(local_path, "example_models", test_subfolder, prism_primitive_csv_file_wrong_keys) + with pytest.raises(ValueError): + self.aedtapp.modeler.import_primitives_from_file(input_file=primitive_file) + primitive_file = os.path.join( + local_path, "example_models", test_subfolder, prism_primitive_csv_file_missing_values + ) + with pytest.raises(ValueError): + self.aedtapp.modeler.import_primitives_from_file(input_file=primitive_file) + + def test_91_primitives_builder(self, add_app): + from pyaedt.generic.DataHandlers import json_to_dict + from pyaedt.modeler.cad.Primitives import PrimitivesBuilder + + ipk = add_app(application=Icepak) + + primitive_file = os.path.join(local_path, "example_models", test_subfolder, primitive_json_file) + primitive_dict = json_to_dict(primitive_file) + + with pytest.raises(TypeError): + PrimitivesBuilder(ipk) + + del primitive_dict["Primitives"] + with pytest.raises(AttributeError): + PrimitivesBuilder(ipk, input_dict=primitive_dict) + + primitive_dict = json_to_dict(primitive_file) + del primitive_dict["Coordinate Systems"][0]["Name"] + primitives_builder = PrimitivesBuilder(ipk, input_dict=primitive_dict) + assert not primitives_builder.create() + + primitive_dict = json_to_dict(primitive_file) + del primitive_dict["Coordinate Systems"][0]["Mode"] + primitives_builder = PrimitivesBuilder(ipk, input_dict=primitive_dict) + assert not primitives_builder.create() + + primitive_dict = json_to_dict(primitive_file) + del primitive_dict["Coordinate Systems"][0]["Origin"] + del primitive_dict["Coordinate Systems"][0]["Y Point"] + del primitive_dict["Coordinate Systems"][1]["Phi"] + del primitive_dict["Coordinate Systems"][1]["Theta"] + primitive_dict["Coordinate Systems"][1]["Mode"] = "Euler Angle ZXZ" + primitives_builder = PrimitivesBuilder(ipk, input_dict=primitive_dict) + del primitives_builder.coordinate_systems[0]["X Axis"] + del primitives_builder.coordinate_systems[1]["Psi"] + primitive_names = primitives_builder.create() + assert len(primitive_names) == 9 + + ipk.modeler.coordinate_systems[0].delete() + ipk.modeler.coordinate_systems[0].delete() + + primitive_dict = json_to_dict(primitive_file) + primitives_builder = PrimitivesBuilder(ipk, input_dict=primitive_dict) + del primitives_builder.instances[0]["Name"] + assert not primitives_builder.create() + assert len(primitive_names) == 9 + + primitive_dict = json_to_dict(primitive_file) + primitives_builder = PrimitivesBuilder(ipk, input_dict=primitive_dict) + del primitives_builder.instances[0]["Coordinate System"] + primitive_names = primitives_builder.create() + assert len(primitive_names) == 9 + ipk.modeler.coordinate_systems[0].delete() + ipk.modeler.coordinate_systems[0].delete() + + primitive_dict = json_to_dict(primitive_file) + primitives_builder = PrimitivesBuilder(ipk, input_dict=primitive_dict) + primitives_builder.instances[0]["Coordinate System"] = "Invented" + assert not primitives_builder.create() + + primitive_dict = json_to_dict(primitive_file) + primitives_builder = PrimitivesBuilder(ipk, input_dict=primitive_dict) + del primitives_builder.instances[0]["Origin"] + primitive_names = primitives_builder.create() + assert len(primitive_names) == 9 + + q2d = add_app(application=Q2d) + primitive_dict = json_to_dict(primitive_file) + primitives_builder = PrimitivesBuilder(q2d, input_dict=primitive_dict) + primitive_names = primitives_builder.create() + assert all(element is None for element in primitive_names) diff --git a/doc/source/Resources/primitive_example.json b/doc/source/Resources/primitive_example.json new file mode 100644 index 00000000000..a2e059ebdab --- /dev/null +++ b/doc/source/Resources/primitive_example.json @@ -0,0 +1,114 @@ +{ + "Primitives": + [ + { + "Primitive Type": "Cylinder", + "Name": "Small", + "Plane": 1, + "Height": 10.0, + "Radius": "3.0mm", + "Internal Radius": 1.0, + "Number of Segments": 0 + }, + { + "Primitive Type": "Cylinder", + "Name": "Big", + "Plane": 0, + "Height": 50.0, + "Radius": 10.0, + "Internal Radius": 0 + }, + { + "Primitive Type": "Cylinder", + "Name": "Big_hole", + "Plane": 0, + "Height": 50.0, + "Radius": 10.0, + "Internal Radius": 5 + }, + { + "Primitive Type": "Box", + "Name": "SmallBox", + "X Length": 10.0, + "Y Length": 10.0, + "Z Length": 10.0 + }, + { + "Primitive Type": "Box", + "Name": "BigBox", + "X Length": 100.0, + "Y Length": 100.0, + "Z Length": 100.0 + } + + ], + "Instances": + [ + { + "Name":"Small", + "Coordinate System": "CS1", + "Origin": ["0mm","1.052dm","2"] + }, + { + "Name":"Small", + "Coordinate System": "Global", + "Origin": ["20um",10,"2.2"] + }, + { + "Name":"Big", + "Coordinate System": "CS2", + "Origin": ["2.0mm",0,50.5] + }, + { + "Name":"Big", + "Coordinate System": "Global", + "Origin": [10,10,10] + }, + { + "Name":"Big_hole", + "CS": "CS1", + "Origin": [0,0,50] + }, + { + "Name":"SmallBox", + "Coordinate System": "CS1", + "Origin": ["0mm","1.052dm","2"] + }, + { + "Name":"SmallBox", + "Coordinate System": "Global", + "Origin": ["20um",10,"2.2"] + }, + { + "Name":"BigBox", + "Coordinate System": "CS2", + "Origin": ["2.0mm",0,50.5] + }, + { + "Name":"BigBox", + "Coordinate System": "Global", + "Origin": [10,10,10] + } + ], + "Coordinate Systems": + [ + { + "Name":"CS1", + "Reference CS": "Global", + "Mode": "Axis/Position", + "Origin": ["20um",10,"2.2"], + "X Axis": [1,0,0], + "Y Point": [0,1,0] + }, + { + "Name": "CS2", + "Reference CS": "Global", + "Mode": "Euler Angle ZYZ", + "Origin": ["1.5",0,"0"], + "Phi": "0deg", + "Theta": 2, + "Psi": 0.5 + } + ], + "Units": "mm" +} \ No newline at end of file diff --git a/doc/source/User_guide/pyaedt_file_data/primitives.rst b/doc/source/User_guide/pyaedt_file_data/primitives.rst index 4c213ddb562..0b9652866db 100644 --- a/doc/source/User_guide/pyaedt_file_data/primitives.rst +++ b/doc/source/User_guide/pyaedt_file_data/primitives.rst @@ -1,2 +1,16 @@ Primitives file =============== +The primitives configuration file allows you to create primitive shapes from a JSON file. + +This code creates primitive shapes from the JSON file: + +.. code:: python + + from pyaedt import Icepak + ipk = Icepak() + ipk.modeler.import_primitives_from_file("primitive_example.json") + ipk.release_desktop() + +File structure example: + +:download:`Primitive example <../../Resources/primitive_example.json>` diff --git a/pyaedt/generic/configurations.py b/pyaedt/generic/configurations.py index c4be7a23379..58eec4a551f 100644 --- a/pyaedt/generic/configurations.py +++ b/pyaedt/generic/configurations.py @@ -639,8 +639,9 @@ def set_all_import(self): class ImportResults(object): - """Import Results Class. - Contains the results of the import operations. Each reusult can be ``True`` or ``False``. + """Contains the results of the import operations. + + Each result can be ``True`` or ``False``. """ def __init__(self): diff --git a/pyaedt/modeler/cad/Primitives.py b/pyaedt/modeler/cad/Primitives.py index 39f6d6f3723..5978f5b278c 100644 --- a/pyaedt/modeler/cad/Primitives.py +++ b/pyaedt/modeler/cad/Primitives.py @@ -15,6 +15,7 @@ from pyaedt.application.Variables import Variable from pyaedt.application.Variables import decompose_variable_value +from pyaedt.generic.DataHandlers import json_to_dict from pyaedt.generic.constants import AEDT_UNITS from pyaedt.generic.general_methods import _dim_arg from pyaedt.generic.general_methods import _uname @@ -4650,6 +4651,32 @@ def import_spaceclaim_document(self, SCFile): self.refresh_all_ids() return True + @pyaedt_function_handler() + def import_primitives_from_file(self, input_file=None, input_dict=None): + """Import and create primitives from a JSON file or dictionary of properties. + + Parameters + ---------- + input_file : str, optional + Path to a JSON file containing report settings. + input_dict : dict, optional + Dictionary containing report settings. + + Returns + ------- + list + List of created primitives. + + Examples + -------- + >>> from pyaedt import Icepak + >>> aedtapp = Icepak() + >>> aedtapp.modeler.import_primitives_from_file(r'C:\temp\primitives.json') + """ + primitives_builder = PrimitivesBuilder(self._app, input_file, input_dict) + primitive_names = primitives_builder.create() + return primitive_names + @pyaedt_function_handler() def modeler_variable(self, value): """Modeler variable. @@ -8221,3 +8248,551 @@ def convert_segments_to_line(self, object_name): ] ) return True + + +class PrimitivesBuilder(object): + """Create primitives from a JSON file or dictionary of properties. + + Parameters + ---------- + app : + Inherited parent object. + input_file : str, optional + Path to a JSON file containing primitive settings. + input_dict : dict, optional + Dictionary containing primitive settings. + + Returns + ------- + :class:`pyaedt.modeler.cad.PrimitivesBuilder` + Primitives builder object if successful. + + Examples + -------- + >>> from pyaedt import Hfss + >>> from pyaedt.modeler.cad.Primitives import PrimitivesBuilder + >>> aedtapp = Hfss() + >>> primitive_file = "primitives_file.json" + >>> primitives_builder = PrimitivesBuilder(aedtapp, input_file=primitive_file) + >>> primitives_builder.create() + >>> aedtapp.release_desktop() + """ + + def __init__(self, app, input_file=None, input_dict=None): + self._app = app + props = {} + if not input_dict and not input_file: # pragma: no cover + msg = "Either a JSON file or a dictionary must be passed as input." + self.logger.error(msg) + raise TypeError(msg) + elif input_file: + file_format = os.path.splitext(os.path.basename(input_file))[1] + if file_format == ".json": + props = json_to_dict(input_file) + elif file_format == ".csv": + import re + + from pyaedt.generic.general_methods import read_csv_pandas + + csv_data = read_csv_pandas(filename=input_file) + primitive_type = csv_data.columns[0] + primitive_type_cleaned = re.sub(r"^#\s*", "", primitive_type) + + if primitive_type_cleaned in ["Blocks Cylinder", "Cylinder"]: + props = self._read_csv_cylinder_props(csv_data) + if primitive_type_cleaned in ["Blocks Prism", "Prism"]: + props = self._read_csv_prism_props(csv_data) + if not props: + msg = "CSV file not valid." + self.logger.error(msg) + raise TypeError(msg) + else: + msg = "Format is not valid." + self.logger.error(msg) + raise TypeError(msg) + else: + props = input_dict + + if not props or not all(key in props for key in ["Primitives", "Instances"]): + msg = "Input data is wrong." + self.logger.error(msg) + raise AttributeError(msg) + + if "Units" in props: + self.units = props["Units"] + else: + self.units = "mm" + self._app.modeler.units = self.units + self.primitives = props["Primitives"] + self.instances = props["Instances"] + self.coordinate_systems = None + if "Coordinate Systems" in props: + self.coordinate_systems = props["Coordinate Systems"] + + @property + def logger(self): + """Logger.""" + return self._app.logger + + @pyaedt_function_handler() + def create(self): + """Create instances of defined primitives. + + Returns + ------- + list + List of instance names created. + """ + created_instances = [] + + if self.coordinate_systems: + cs_flag = self._create_coordinate_system() + if not cs_flag: + self.logger.error("Wrong coordinate system is defined.") + return False + + cs_names = [cs.name for cs in self._app.modeler.coordinate_systems] + + for instance_data in self.instances: + name = instance_data.get("Name") + if not name: + self.logger.error("``Name`` parameter is not defined.") + return False + + cs = instance_data.get("Coordinate System") + if not cs: + self.logger.warning("``Coordinate System`` parameter is not defined, ``Global`` is assigned.") + instance_data["Coordinate System"] = "Global" + cs = instance_data.get("Coordinate System") + elif instance_data["Coordinate System"] != "Global" and instance_data["Coordinate System"] not in cs_names: + self.logger.error("Coordinate system {} does not exist.".format(cs)) + return False + + origin = instance_data.get("Origin") + if not origin: + self.logger.warning("``Origin`` parameter not defined. ``[0, 0, 0]`` is assigned.") + instance_data["Origin"] = [0, 0, 0] + origin = instance_data.get("Origin") + else: + origin = self.convert_units(origin) + + primitive_data = next((primitive for primitive in self.primitives if primitive["Name"] == name), None) + + if primitive_data: + instance = self._create_instance(name, cs, origin, primitive_data) + created_instances.append(instance) + + return created_instances + + @pyaedt_function_handler() + def _create_instance(self, name, cs, origin, primitive_data): + """Create a primitive instance. + + This method determines the primitive type and creates an instance based on this type. + + Parameters + ---------- + name : str + Name for the primitive. + cs : str + Reference coordinate system. + origin : list + Instance origin position. + primitive_data : dict + Primitive information. + + Returns + ------- + str + Instance name. + """ + primitive_type = primitive_data["Primitive Type"] + instance = None + if primitive_type == "Cylinder": + if self._app.modeler._is3d: + instance = self._create_cylinder_instance(name, cs, origin, primitive_data) + if primitive_type == "Box": + if self._app.modeler._is3d: + instance = self._create_box_instance(name, cs, origin, primitive_data) + + if not instance: + self.logger.warning("Primitive type: {} is unsupported.".format(primitive_type)) + return None + + return instance + + @pyaedt_function_handler() + def _create_cylinder_instance(self, name, cs, origin, data): + """Create a cylinder instance. + + Parameters + ---------- + name : str + Name for the primitive. + cs : str + Reference coordinate system. + origin : list + Instance origin position. + data : dict + Cylinder information. + + Returns + ------- + str + Instance name. + """ + if not data.get("Plane"): + data["Plane"] = 0 + if not data.get("Radius"): + data["Radius"] = 10 + if not data.get("Height"): + data["Height"] = 50 + if not data.get("Number of Segments"): + data["Number of Segments"] = 0 + + self._app.modeler.set_working_coordinate_system(cs) + + cyl1 = self._app.modeler.create_cylinder( + cs_axis=data.get("Plane"), + position=origin, + radius=data.get("Radius"), + height=data.get("Height"), + numSides=int(data.get("Number of Segments")), + name=name, + ) + + internal_radius = data.get("Internal Radius") + if internal_radius: + internal_radius = self.convert_units([internal_radius])[0] + radius = self.convert_units([data.get("Radius")])[0] + if internal_radius > radius: + self.logger.warning("Internal radius is larger than external radius.") + elif internal_radius != 0: + cyl2 = self._app.modeler.create_cylinder( + cs_axis=data.get("Plane"), + position=origin, + radius=internal_radius, + height=data.get("Height"), + numSides=data.get("Number of Segments"), + name=name, + ) + self._app.modeler.subtract(blank_list=cyl1, tool_list=cyl2, keep_originals=False) + + return cyl1 + + def _create_box_instance(self, name, cs, origin, data): + """Create a box instance. + + Parameters + ---------- + name : str + Name for the primitive. + cs : str + Reference coordinate system. + origin : list + Instance origin position. + data : dict + Box information. + + Returns + ------- + str + Instance name. + """ + if not data.get("X Length"): + data["X Length"] = 10 + if not data.get("Y Length"): + data["Y Length"] = 10 + if not data.get("Z Length"): + data["Z Length"] = 10 + + self._app.modeler.set_working_coordinate_system(cs) + + box1 = self._app.modeler.create_box( + position=origin, + dimensions_list=[data["X Length"], data["Y Length"], data["Z Length"]], + name=name, + ) + return box1 + + @pyaedt_function_handler() + def _read_csv_cylinder_props(self, csv_data): + """Convert CSV data to ``PrimitivesBuilder`` properties. + + Create a cylinder instance. + + Parameters + ---------- + csv_data : :class:`pandas.DataFrame` + + Returns + ------- + dict + PrimitivesBuilder properties. + """ + primitive_props = { + "Primitive Type": "Cylinder", + "Name": "", + "Plane": 0, + "Height": 1.0, + "Radius": 2, + "Internal Radius": 0.0, + "Number of Segments": 0, + } + instances_props = {"Name": "", "Coordinate System": "Global", "Origin": [0, 0, 0]} + required_csv_keys = ["name", "xc", "yc", "zc", "plane", "radius", "iradius", "height"] + # Take the keys + csv_keys = [] + index_row = 0 + for index_row, row in csv_data.iterrows(): + if "#" not in row.iloc[0]: + csv_keys = row.array.dropna() + csv_keys = csv_keys.tolist() + break + + if not all(k in required_csv_keys for k in csv_keys): + msg = "The column names in the CSV file do not match the expected names." + self.logger.error(msg) + raise ValueError + # Create instances and primitives + props_cyl = {} + row_cont = 0 + for index_row_new, row in csv_data.iloc[index_row + 1 :].iterrows(): + row_info = row.dropna().values + if len(row_info) != len(csv_keys): + msg = "Values missing in the CSV file " + self.logger.error(msg) + raise ValueError + + if not props_cyl: + props_cyl = {"Primitives": [primitive_props], "Instances": [instances_props]} + else: + props_cyl["Primitives"].append(primitive_props.copy()) + props_cyl["Instances"].append(instances_props.copy()) + + col_cont = 0 + # Check for nan values in each column + for value in row_info: + if csv_keys[col_cont] == "name": + props_cyl["Primitives"][row_cont]["Name"] = str(value) + props_cyl["Instances"][row_cont]["Name"] = str(value) + elif csv_keys[col_cont] == "xc": + props_cyl["Instances"][row_cont]["Origin"][0] = float(value) + elif csv_keys[col_cont] == "yc": + props_cyl["Instances"][row_cont]["Origin"][1] = float(value) + elif csv_keys[col_cont] == "zc": + props_cyl["Instances"][row_cont]["Origin"][2] = float(value) + elif csv_keys[col_cont] == "plane": + props_cyl["Primitives"][row_cont]["Plane"] = int(value) + elif csv_keys[col_cont] == "radius": + props_cyl["Primitives"][row_cont]["Radius"] = float(value) + elif csv_keys[col_cont] == "iradius": + props_cyl["Primitives"][row_cont]["Internal Radius"] = float(value) + elif csv_keys[col_cont] == "height": + props_cyl["Primitives"][row_cont]["Height"] = float(value) + col_cont += 1 + row_cont += 1 + return props_cyl + + @pyaedt_function_handler() + def _read_csv_prism_props(self, csv_data): + """Convert CSV data to ``PrimitivesBuilder`` properties. + + Create a box instance. + + Parameters + ---------- + csv_data : :class:`pandas.DataFrame` + + Returns + ------- + dict + PrimitivesBuilder properties. + """ + primitive_props = { + "Primitive Type": "Box", + "Name": "", + "X Length": 0, + "Y Length": 0, + "Z Length": 0, + } + instances_props = {"Name": "", "Coordinate System": "Global", "Origin": [0, 0, 0]} + required_csv_keys = ["name", "xs", "ys", "zs", "xd", "yd", "zd"] + # Take the keys + csv_keys = [] + index_row = 0 + for index_row, row in csv_data.iterrows(): + if "#" not in row.iloc[0]: + csv_keys = row.array.dropna() + csv_keys = csv_keys.tolist() + break + + if not all(k in required_csv_keys for k in csv_keys): + msg = "The column names in the CSV file do not match the expected names." + self.logger.error(msg) + raise ValueError + # Create instances and primitives + props_box = {} + row_cont = 0 + for index_row_new, row in csv_data.iloc[index_row + 1 :].iterrows(): + row_info = row.dropna().values + if len(row_info) != len(csv_keys): + msg = "Values missing in the CSV file " + self.logger.error(msg) + raise ValueError + + if not props_box: + props_box = {"Primitives": [primitive_props], "Instances": [instances_props]} + else: + props_box["Primitives"].append(primitive_props.copy()) + props_box["Instances"].append(instances_props.copy()) + + col_cont = 0 + # Check for nan values in each column + for value in row_info: + if csv_keys[col_cont] == "name": + props_box["Primitives"][row_cont]["Name"] = str(value) + props_box["Instances"][row_cont]["Name"] = str(value) + elif csv_keys[col_cont] == "xs": + props_box["Instances"][row_cont]["Origin"][0] = float(value) + elif csv_keys[col_cont] == "ys": + props_box["Instances"][row_cont]["Origin"][1] = float(value) + elif csv_keys[col_cont] == "zs": + props_box["Instances"][row_cont]["Origin"][2] = float(value) + elif csv_keys[col_cont] == "xd": + props_box["Primitives"][row_cont]["X Length"] = float(value) + elif csv_keys[col_cont] == "yd": + props_box["Primitives"][row_cont]["Y Length"] = float(value) + elif csv_keys[col_cont] == "zd": + props_box["Primitives"][row_cont]["Z Length"] = float(value) + col_cont += 1 + row_cont += 1 + return props_box + + @pyaedt_function_handler() + def convert_units(self, values): + """Convert input values to default units. + + If a value has units, convert it to a numeric value with the default units. + + Parameters + ---------- + values : list + List of values. + + Returns + ------- + list + List of numeric values. + """ + extracted_values = [] + for value in values: + if isinstance(value, (int, float)): + extracted_values.append(value) + elif isinstance(value, str): + value_number, units = decompose_variable_value(value) + if units: + value_number = self._length_unit_conversion(value_number, units) + extracted_values.append(value_number) + + return extracted_values + + @pyaedt_function_handler() + def _length_unit_conversion(self, value, input_units): + """Convert value to input units.""" + from pyaedt.generic.constants import unit_converter + + converted_value = unit_converter(value, unit_system="Length", input_units=input_units, output_units=self.units) + return converted_value + + @pyaedt_function_handler() + def _create_coordinate_system(self): + """Create a coordinate system defined in the object.""" + for cs in self.coordinate_systems: + cs_names = [cs.name for cs in self._app.modeler.coordinate_systems] + name = cs.get("Name") + if not name: + self.logger.warning("Coordinate system does not have a 'Name' parameter.") + return False + if name in cs_names: + self.logger.warning("Coordinate system {} already exists.".format(name)) + continue + mode = cs.get("Mode") + if not mode or not any(key in mode for key in ["Axis/Position", "Euler Angle ZYZ", "Euler Angle ZXZ"]): + self.logger.warning( + "Coordinate system does not have a 'Mode' parameter or it is not valid. " + "Options are 'Axis/Position', 'Euler Angle ZYZ', and 'Euler Angle ZXZ'." + ) + return False + + origin = cs.get("Origin") + reference_cs = cs.get("Reference CS") + if not origin: + origin = [0, 0, 0] + cs["Origin"] = origin + else: + origin = self.convert_units(origin) + + if not reference_cs: + reference_cs = "Global" + cs["Reference CS"] = reference_cs + + if mode == "Axis/Position": + x_axis = cs.get("X Axis") + y_point = cs.get("Y Point") + + if not x_axis: + x_axis = [1, 0, 0] + cs["X Axis"] = x_axis + if not y_point: + y_point = [0, 1, 0] + cs["Y Point"] = y_point + new_cs = self._app.modeler.create_coordinate_system( + origin=origin, + reference_cs=reference_cs, + name=name, + mode="axis", + x_pointing=x_axis, + y_pointing=y_point, + psi=0, + theta=0, + phi=0, + ) + cs["Name"] = new_cs.name + else: + phi = cs.get("Phi") + theta = cs.get("Theta") + psi = cs.get("Psi") + + if not phi: + phi = "0deg" + cs["Phi"] = phi + elif isinstance(phi, (int, float)): + phi = str(phi) + "deg" + cs["Phi"] = phi + + if not theta: + theta = "0deg" + cs["Theta"] = theta + elif isinstance(theta, (int, float)): + theta = str(theta) + "deg" + cs["Theta"] = theta + + if not psi: + psi = "0deg" + cs["Psi"] = psi + elif isinstance(psi, (int, float)): + psi = str(psi) + "deg" + cs["Psi"] = psi + + if mode == "Euler Angle ZYZ": + cs_mode = "zyz" + else: + cs_mode = "zxz" + + new_cs = self._app.modeler.create_coordinate_system( + origin=origin, reference_cs=reference_cs, name=name, mode=cs_mode, psi=psi, theta=theta, phi=phi + ) + cs["Name"] = new_cs.name + + return True From 61f4d81b010d6d49802626f0390412903c3a9298 Mon Sep 17 00:00:00 2001 From: Samuel Lopez <85613111+Samuelopez-ansys@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:02:55 +0100 Subject: [PATCH 10/10] New dev version (#4279) Co-authored-by: SMoraisAnsys <146729917+SMoraisAnsys@users.noreply.github.com> --- codecov.yml | 1 - pyaedt/__init__.py | 2 +- pyaedt/generic/design_types.py | 2 +- pyaedt/generic/process.py | 286 ---------------------------- pyaedt/siwave.py | 336 --------------------------------- pyproject.toml | 2 +- 6 files changed, 3 insertions(+), 626 deletions(-) delete mode 100644 pyaedt/generic/process.py delete mode 100644 pyaedt/siwave.py diff --git a/codecov.yml b/codecov.yml index 83f3e1b78d7..459a5b5df06 100644 --- a/codecov.yml +++ b/codecov.yml @@ -20,7 +20,6 @@ coverage: - "pyaedt/rpc/**/*.py" # ignore folders and all its contents - "pyaedt/generic/toolkit.py" # ignore folders and all its contents - "pyaedt/doctest_fixtures/*.py" # ignore folders and all its contents - - "pyaedt/siwave.py" # ignore folders and all its contents - "pyaedt/setup.py" # ignore folders and all its contents - "pyaedt/setup-distutils.py" # ignore folders and all its contents - "pyaedt/_setup_common.py" # ignore folders and all its contents diff --git a/pyaedt/__init__.py b/pyaedt/__init__.py index a4dc7d0b4fc..6274f54bd48 100644 --- a/pyaedt/__init__.py +++ b/pyaedt/__init__.py @@ -40,7 +40,7 @@ def custom_show_warning(message, category, filename, lineno, file=None, line=Non # pyaedt_path = os.path.dirname(__file__) -__version__ = "0.8.dev0" +__version__ = "0.9.dev0" version = __version__ # diff --git a/pyaedt/generic/design_types.py b/pyaedt/generic/design_types.py index 6d17bc8201d..52146db27c6 100644 --- a/pyaedt/generic/design_types.py +++ b/pyaedt/generic/design_types.py @@ -1723,7 +1723,7 @@ def Siwave( specified_version=None, ): """Siwave Class.""" - from pyaedt.siwave import Siwave as app + from pyedb.siwave import Siwave as app return app( specified_version=specified_version, diff --git a/pyaedt/generic/process.py b/pyaedt/generic/process.py deleted file mode 100644 index ca3786d65d5..00000000000 --- a/pyaedt/generic/process.py +++ /dev/null @@ -1,286 +0,0 @@ -import os.path - -from pyaedt import is_ironpython -from pyaedt import is_linux -from pyaedt.generic.general_methods import env_path - -if is_linux and is_ironpython: - import subprocessdotnet as subprocess -else: - import subprocess - - -class SiwaveSolve(object): - def __init__(self, aedb_path="", aedt_version="2021.2", aedt_installer_path=None): - self._project_path = aedb_path - self._exec_path = "" - self._nbcores = 4 - self._ng = True - if aedt_installer_path: - self.installer_path = aedt_installer_path - else: - try: - self.installer_path = env_path(aedt_version) - except: - raise Exception("Either a valid aedt version or full path has to be provided") - if self._ng: - executable = "siwave_ng" - else: - executable = "siwave" - if is_linux: - self._exe = os.path.join(self.installer_path, executable) - else: - self._exe = os.path.join(self.installer_path, executable + ".exe") - - @property - def siwave_exe(self): - return self._exe - - @siwave_exe.setter - def siwave_exe(self, value): - self._exe = value - - @property - def projectpath(self): - return self._project_path - - @projectpath.setter - def projectpath(self, value): - self._project_path = value - - @property - def execfile(self): - return self._exec_path - - @execfile.setter - def execfile(self, value): - self._exec_path = value - - @property - def nbcores(self): - return self._nbcores - - @nbcores.setter - def nbcores(self, value): - self._nbcores = value - - @property - def nongraphical(self): - return self._ng - - @nongraphical.setter - def nongraphical(self, value): - self._ng = value - - def solve(self): - # supporting non graphical solve only - if self.nongraphical: - if is_linux: - exe_path = os.path.join(self.installer_path, "siwave_ng") - else: - exe_path = os.path.join(self.installer_path, "siwave_ng.exe") - exec_file = os.path.splitext(self._project_path)[0] + ".exec" - if os.path.exists(exec_file): - with open(exec_file, "r+") as f: - content = f.readlines() - if "SetNumCpus" not in content: - f.writelines(str.format("SetNumCpus {}", str(self.nbcores)) + "\n") - f.writelines("SaveSiw") - else: - fstarts = [i for i in range(len(content)) if content[i].startswith("SetNumCpus")] - content[fstarts[0]] = str.format("SetNumCpus {}", str(self.nbcores)) - f.close() - os.remove(exec_file) - f = open(exec_file, "w") - f.writelines(content) - command = [exe_path] - command.append(self._project_path) - command.append(exec_file) - command.append("-formatOutput -useSubdir") - p = subprocess.Popen(" ".join(command)) - p.wait() - - def export_3d_cad( - self, format_3d="Q3D", output_folder=None, net_list=None, num_cores=None, aedt_file_name=None, hidden=False - ): - """Export edb to Q3D or HFSS - - Parameters - ---------- - format_3d : str, default ``Q3D`` - output_folder : str - Output file folder. If `` then the aedb parent folder is used - net_list : list, default ``None`` - Define Nets to Export. if None, all nets will be exported - num_cores : int, optional - Define number of cores to use during export - aedt_file_name : str, optional - Output aedt file name (without .aedt extension). If `` then default naming is used - Returns - ------- - str - path to aedt file - """ - if not output_folder: - output_folder = os.path.dirname(self.projectpath) - scriptname = os.path.join(output_folder, "export_cad.py") - with open(scriptname, "w") as f: - f.write("import os\n") - f.write("edbpath = r'{}'\n".format(self.projectpath)) - f.write("exportOptions = os.path.join(r'{}', 'options.config')\n".format(output_folder)) - f.write("oDoc.ScrImportEDB(edbpath)\n") - f.write("oDoc.ScrSaveProjectAs(os.path.join(r'{}','{}'))\n".format(output_folder, "test.siw")) - if net_list: - f.write("allnets = []\n") - for el in net_list: - f.write("allnets.append('{}')\n".format(el)) - f.write("for i in range(0, len(allnets)):\n") - f.write(" if allnets[i] != 'DUMMY':\n") - f.write(" oDoc.ScrSelectNet(allnets[i], 1)\n") - f.write("oDoc.ScrSetOptionsFor3DModelExport(exportOptions)\n") - if not aedt_file_name: - aedt_file_name = format_3d + "_siwave.aedt" - f.write("q3d_filename = os.path.join(r'{}', '{}')\n".format(output_folder, aedt_file_name)) - if num_cores: - f.write("oDoc.ScrSetNumCpusToUse('{}')\n".format(num_cores)) - self.nbcores = num_cores - f.write("oDoc.ScrExport3DModel('{}', q3d_filename)\n".format(format_3d)) - f.write("oDoc.ScrCloseProject()\n") - f.write("oApp.Quit()\n") - if is_linux: - _exe = '"' + os.path.join(self.installer_path, "siwave") + '"' - else: - _exe = '"' + os.path.join(self.installer_path, "siwave.exe") + '"' - command = [_exe] - if hidden: - command.append("-embedding") - command.append("-RunScriptAndExit") - command.append(scriptname) - print(command) - os.system(" ".join(command)) - # p1 = subprocess.call(" ".join(command)) - # p1.wait() - return os.path.join(output_folder, aedt_file_name) - - def export_dc_report( - self, - siwave_project, - solution_name, - output_folder=None, - html_report=True, - vias=True, - voltage_probes=True, - current_sources=True, - voltage_sources=True, - power_tree=True, - loop_res=True, - hidden=True, - ): - """Close EDB and solve it with Siwave. - - Parameters - ---------- - siwave_project : str - Siwave full project name. - solution_name : str - Siwave DC Analysis name. - output_folder : str, optional - Ouptu folder where files will be downloaded. - html_report : bool, optional - Either if generate or not html report. Default is `True`. - vias : bool, optional - Either if generate or not vias report. Default is `True`. - voltage_probes : bool, optional - Either if generate or not voltage probe report. Default is `True`. - current_sources : bool, optional - Either if generate or not current source report. Default is `True`. - voltage_sources : bool, optional - Either if generate or not voltage source report. Default is `True`. - power_tree : bool, optional - Either if generate or not power tree image. Default is `True`. - loop_res : bool, optional - Either if generate or not loop resistance report. Default is `True`. - Returns - ------- - list - list of files generated. - """ - if not output_folder: - output_folder = os.path.dirname(self.projectpath) - output_list = [] - scriptname = os.path.normpath(os.path.join(os.path.normpath(output_folder), "export_results.py")) - with open(scriptname, "w") as f: - f.write("oApp.OpenProject(r'{}')\n".format(siwave_project)) - if html_report: - f.write("proj = oApp.GetActiveProject()\n") - - f.write("proj.ScrExportDcSimReportColorBarProperties(14,2,False,True)\n") - - f.write("proj.ScrExportDcSimReportScaling('All','All',-1,-1,False)\n") - report_name = os.path.join(output_folder, solution_name + ".htm") - f.write("proj.ScrExportDcSimReport('{}','White',r'{}')\n".format(solution_name, report_name)) - output_list.append(report_name) - if vias: - via_name = os.path.join(output_folder, "vias.txt") - f.write("proj.ScrExportElementData('{}',r'{}','Vias')\n".format(solution_name, via_name)) - output_list.append(via_name) - - if voltage_probes: - probes_names = os.path.join(output_folder, "voltage_probes.txt") - f.write("proj.ScrExportElementData('{}',r'{}','Voltage Probes')\n".format(solution_name, probes_names)) - output_list.append(probes_names) - - if current_sources: - source_name = os.path.join(output_folder, "current_sources.txt") - - f.write("proj.ScrExportElementData('{}',r'{}','Current Sources')\n".format(solution_name, source_name)) - output_list.append(source_name) - - if voltage_sources: - sources = os.path.join(output_folder, "v_sources.txt") - - f.write("proj.ScrExportElementData('{}',r'{}','Voltage Sources')\n".format(solution_name, sources)) - output_list.append(sources) - - if power_tree: - csv_file = os.path.join(output_folder, "powertree.csv") - c = open(csv_file, "w") - c.close() - png_file = os.path.join(output_folder, "powertree.png") - f.write("proj.ScrExportDcPowerTree('{}',r'{}', r'{}' )\n".format(solution_name, csv_file, png_file)) - output_list.append(png_file) - - if loop_res: - f.write("sourceNames=[]\n") - f.write("sourceData=[]\n") - f.write("proj.ScrReadDCLoopResInfo('{}', sourceNames, sourceData)\n".format(solution_name)) - loop_res = os.path.join(output_folder, "loop_res.txt") - f.write("with open(r'{}','w') as f:\n".format(loop_res)) - - f.write(" f.writelines('Sources\tValue\\n')\n") - - f.write(" for a, b in zip(sourceNames, sourceData):\n") - - f.write(" f.writelines(a + '\t' + b + '\\n')\n") - output_list.append(loop_res) - - f.write("proj.ScrCloseProject()\n") - - f.write("oApp.Quit()\n") - if is_linux: - _exe = '"' + os.path.join(self.installer_path, "siwave") + '"' - else: - _exe = '"' + os.path.join(self.installer_path, "siwave.exe") + '"' - command = [_exe] - if hidden: - command.append("-embedding") - command.append("-RunScriptAndExit") - command.append('"' + scriptname + '"') - print(command) - if is_linux: - p = subprocess.Popen(command) - - else: - p = subprocess.Popen(" ".join(command)) - p.wait() - return output_list diff --git a/pyaedt/siwave.py b/pyaedt/siwave.py deleted file mode 100644 index 14e7bc35b75..00000000000 --- a/pyaedt/siwave.py +++ /dev/null @@ -1,336 +0,0 @@ -""" -This module contains the ``Siwave`` class. - -The ``Siwave`` module can be initialized as standalone before launching an app or -automatically initialized by an app to the latest installed AEDT version. - -""" - -from __future__ import absolute_import # noreorder - -import os -import pkgutil -import sys -import time - -from pyaedt.generic.clr_module import _clr -from pyaedt.generic.general_methods import _pythonver -from pyaedt.generic.general_methods import is_ironpython -from pyaedt.generic.general_methods import is_windows -from pyaedt.generic.general_methods import pyaedt_function_handler -from pyaedt.misc import list_installed_ansysem - - -class Siwave(object): - """Initializes SIwave based on the inputs provided and manages SIwave release and closing. - - Parameters - ---------- - specified_version : str, int, float, optional - Version of AEDT to use. The default is ``None``, in which case - the active setup is used or the latest installed version is used. - - """ - - @property - def version_keys(self): - """Version keys for AEDT.""" - - self._version_keys = [] - self._version_ids = {} - - version_list = list_installed_ansysem() - for version_env_var in version_list: - current_version_id = version_env_var.replace("ANSYSEM_ROOT", "").replace("ANSYSEMSV_ROOT", "") - version = int(current_version_id[0:2]) - release = int(current_version_id[2]) - if version < 20: - if release < 3: - version -= 1 - else: - release -= 2 - v_key = "20{0}.{1}".format(version, release) - self._version_keys.append(v_key) - self._version_ids[v_key] = version_env_var - return self._version_keys - - @property - def current_version(self): - """Current version of AEDT.""" - return self.version_keys[0] - - def __init__(self, specified_version=None): - if is_ironpython: - _com = "pythonnet" - import System - elif is_windows: # pragma: no cover - modules = [tup[1] for tup in pkgutil.iter_modules()] - if _clr: - import win32com.client - - _com = "pythonnet_v3" - elif "win32com" in modules: - import win32com.client - - _com = "pywin32" - else: - raise Exception("Error. No win32com.client or PythonNET modules are found. They need to be installed.") - self._main = sys.modules["__main__"] - print("Launching Siwave Init") - if "oSiwave" in dir(self._main) and self._main.oSiwave is not None: - self._main.AEDTVersion = self._main.oSiwave.GetVersion()[0:6] - self._main.oSiwave.RestoreWindow() - specified_version = self.current_version - assert specified_version in self.version_keys, "Specified version {} is not known.".format( - specified_version - ) - version_key = specified_version - base_path = os.getenv(self._version_ids[specified_version]) - self._main.sDesktopinstallDirectory = base_path - else: - if specified_version: - assert specified_version in self.version_keys, "Specified version {} is not known.".format( - specified_version - ) - version_key = specified_version - else: - version_key = self.current_version - base_path = os.getenv(self._version_ids[version_key]) - self._main = sys.modules["__main__"] - self._main.sDesktopinstallDirectory = base_path - version = "Siwave.Application." + version_key - self._main.AEDTVersion = version_key - self._main.interpreter = _com - self._main.interpreter_ver = _pythonver - if "oSiwave" in dir(self._main): - del self._main.oSiwave - - if _com == "pythonnet": - self._main.oSiwave = System.Activator.CreateInstance(System.Type.GetTypeFromProgID(version)) - - elif _com == "pythonnet_v3": - # TODO check if possible to use pythonnet. at the moment the tool open AEDt - # but doesn't return the wrapper of oApp - print("Launching Siwave with module win32com.") - - self._main.oSiwave = win32com.client.Dispatch(version) - - self._main.AEDTVersion = version_key - self.oSiwave = self._main.oSiwave - self._main.oSiwave.RestoreWindow() - self._main.siwave_initialized = True - self._oproject = self.oSiwave.GetActiveProject() - pass - - @property - def project_name(self): - """Project name. - - Returns - ------- - str - Name of the project. - - """ - return self._oproject.GetName() - - @property - def project_path(self): - """Project path. - - Returns - ------- - str - Full absolute path for the project. - - """ - return os.path.normpath(self.oSiwave.GetProjectDirectory()) - - @property - def project_file(self): - """Project file. - - Returns - ------- - str - Full absolute path and name for the project file. - - """ - return os.path.join(self.project_path, self.project_name + ".siw") - - @property - def lock_file(self): - """Lock file. - - Returns - ------- - str - Full absolute path and name for the project lock file. - - """ - return os.path.join(self.project_path, self.project_name + ".siw.lock") - - @property - def results_directory(self): - """Results directory. - - Returns - ------- - str - Full absolute path to the ``aedtresults`` directory. - """ - return os.path.join(self.project_path, self.project_name + ".siwresults") - - @property - def src_dir(self): - """Source directory. - - Returns - ------- - str - Full absolute path to the ``python`` directory. - """ - return os.path.dirname(os.path.realpath(__file__)) - - @property - def pyaedt_dir(self): - """PyAEDT directory. - - Returns - ------- - str - Full absolute path to the ``pyaedt`` directory. - """ - return os.path.realpath(os.path.join(self.src_dir, "..")) - - @property - def oproject(self): - """Project.""" - return self._oproject - - @pyaedt_function_handler() - def open_project(self, proj_path=None): - """Open a project. - - Parameters - ---------- - proj_path : str, optional - Full path to the project. The default is ``None``. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - - if os.path.exists(proj_path): - open_result = self.oSiwave.OpenProject(proj_path) - self._oproject = self.oSiwave.GetActiveProject() - return open_result - else: - return False - - @pyaedt_function_handler() - def save_project(self, projectpath=None, projectName=None): - """Save the project. - - Parameters - ---------- - proj_path : str, optional - Full path to the project. The default is ``None``. - projectName : str, optional - Name of the project. The default is ``None``. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - if projectName and projectpath: - self.oproject.ScrSaveProjectAs(os.path.join(projectpath, projectName + ".siw")) - else: - self.oproject.Save() - return True - - @pyaedt_function_handler() - def close_project(self, save_project=False): - """Close the project. - - Parameters - ---------- - save_project : bool, optional - Whether to save the current project before closing it. The default is ``False``. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - if save_project: - self.save_project() - self.oproject.ScrCloseProject() - self._oproject = None - return True - - @pyaedt_function_handler() - def quit_application(self): - """Quit the application. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - self._main.oSiwave.Quit() - return True - - @pyaedt_function_handler() - def export_element_data(self, simulation_name, file_path, data_type="Vias"): - """Export element data. - - Parameters - ---------- - simulation_name : str - Name of the setup. - file_path : str - Path to the exported report. - data_type : str, optional - Type of the data. The default is ``"Vias"``. - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - """ - self.oproject.ScrExportElementData(simulation_name, file_path, data_type) - return True - - @pyaedt_function_handler() - def export_siwave_report(self, simulation_name, file_path, bkground_color="White"): - """Export the Siwave report. - - Parameters - ---------- - simulation_name : str - Name of the setup. - file_path : str - Path to the exported report. - bkground_color : str, optional - Color of the report's background. The default is ``"White"``. - - - Returns - ------- - bool - ``True`` when successful, ``False`` when failed. - - """ - self.oproject.ScrExportDcSimReportScaling("All", "All", -1, -1, False) - self.oproject.ScrExportDcSimReport(simulation_name, bkground_color, file_path) - while not os.path.exists(file_path): - time.sleep(0.1) - return True diff --git a/pyproject.toml b/pyproject.toml index ca37ba594bd..8c6f6539eb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "fpdf2", "jsonschema", "pytomlpp; python_version < '3.12'", - "pyedb==0.2.0" + "pyedb==0.4.0" ] [project.optional-dependencies]