diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index a8170ef3608..7062747303e 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -176,7 +176,7 @@ jobs: .venv\Scripts\Activate.ps1 # Uninstall conflicting dependencies pip uninstall --yes vtk - pip install --extra-index-url https://wheels.vtk.org vtk-osmesa==9.2.20230527.dev0 + pip install --extra-index-url https://wheels.vtk.org vtk-osmesa # TODO: Update this step once pyaedt-examples is ready # NOTE: Use environment variable to keep the doctree and avoid redundant build for PDF pages @@ -259,7 +259,7 @@ jobs: .venv\Scripts\Activate.ps1 # Uninstall conflicting dependencies pip uninstall --yes vtk - pip install --extra-index-url https://wheels.vtk.org vtk-osmesa==9.2.20230527.dev0 + pip install --extra-index-url https://wheels.vtk.org vtk-osmesa - name: Run tests on _unittest_solvers env: @@ -375,7 +375,7 @@ jobs: .venv\Scripts\Activate.ps1 # Uninstall conflicting dependencies pip uninstall --yes vtk - pip install --extra-index-url https://wheels.vtk.org vtk-osmesa==9.2.20230527.dev0 + pip install --extra-index-url https://wheels.vtk.org vtk-osmesa - name: Run tests on _unittest uses: nick-fields/retry@v3 @@ -445,7 +445,7 @@ jobs: source .venv/bin/activate # Uninstall conflicting dependencies pip uninstall --yes vtk - pip install --extra-index-url https://wheels.vtk.org vtk-osmesa==9.2.20230527.dev0 + pip install --extra-index-url https://wheels.vtk.org vtk-osmesa - name: Run tests on _unittest uses: nick-fields/retry@v3 diff --git a/_unittest/test_01_Design.py b/_unittest/test_01_Design.py index 88f9800caa9..6c99de67ee4 100644 --- a/_unittest/test_01_Design.py +++ b/_unittest/test_01_Design.py @@ -379,10 +379,10 @@ def test_32_make_read_only_variable(self): def test_33_aedt_object(self): aedt_obj = AedtObjects() - assert aedt_obj.odesign - assert aedt_obj.oproject + assert aedt_obj._odesign + assert aedt_obj._oproject aedt_obj = AedtObjects(self.aedtapp._desktop_class, self.aedtapp.oproject, self.aedtapp.odesign) - assert aedt_obj.odesign == self.aedtapp.odesign + assert aedt_obj._odesign == self.aedtapp.odesign def test_34_force_project_path_disable(self): settings.force_error_on_missing_project = True diff --git a/_unittest/test_03_Materials.py b/_unittest/test_03_Materials.py index 3779bdae18d..019c420193d 100644 --- a/_unittest/test_03_Materials.py +++ b/_unittest/test_03_Materials.py @@ -408,3 +408,42 @@ def test_14_set_core_loss(self): self.aedtapp.materials["mat_test"].set_coreloss_at_frequency( points_at_frequency={60: [[0, 0], [1, 3.5], [2, 7.4]]}, thickness=50 ) + + def test_15_thermalmodifier_and_spatialmodifier(self): + assert self.aedtapp.materials["vacuum"].conductivity.thermalmodifier is None + assert self.aedtapp.materials["vacuum"].conductivity.spatialmodifier is None + + self.aedtapp.materials["vacuum"].conductivity.thermalmodifier = "1" + assert self.aedtapp.materials["vacuum"].conductivity.thermalmodifier == "1" + self.aedtapp.materials["vacuum"].conductivity.spatialmodifier = "1" + assert self.aedtapp.materials["vacuum"].conductivity.spatialmodifier == "1" + + self.aedtapp.materials["vacuum"].conductivity.thermalmodifier = None + assert self.aedtapp.materials["vacuum"].conductivity.thermalmodifier is None + self.aedtapp.materials["vacuum"].conductivity.thermalmodifier = "2" + assert self.aedtapp.materials["vacuum"].conductivity.thermalmodifier == "2" + + self.aedtapp.materials["vacuum"].conductivity.spatialmodifier = None + assert self.aedtapp.materials["vacuum"].conductivity.spatialmodifier is None + self.aedtapp.materials["vacuum"].conductivity.spatialmodifier = "2" + assert self.aedtapp.materials["vacuum"].conductivity.spatialmodifier == "2" + + self.aedtapp.materials["vacuum"].conductivity.thermalmodifier = None + assert self.aedtapp.materials["vacuum"].conductivity.thermalmodifier is None + self.aedtapp.materials["vacuum"].conductivity.spatialmodifier = None + assert self.aedtapp.materials["vacuum"].conductivity.spatialmodifier is None + + self.aedtapp.materials["vacuum"].conductivity.thermalmodifier = "3" + assert self.aedtapp.materials["vacuum"].conductivity.thermalmodifier == "3" + self.aedtapp.materials["vacuum"].conductivity.spatialmodifier = "3" + assert self.aedtapp.materials["vacuum"].conductivity.spatialmodifier == "3" + + self.aedtapp.materials["vacuum"].conductivity.thermalmodifier = None + self.aedtapp.materials["vacuum"].conductivity.spatialmodifier = None + self.aedtapp.materials["vacuum"].conductivity.thermalmodifier = "4" + assert self.aedtapp.materials["vacuum"].conductivity.thermalmodifier == "4" + self.aedtapp.materials["vacuum"].conductivity.spatialmodifier = "4" + assert self.aedtapp.materials["vacuum"].conductivity.spatialmodifier == "4" + + self.aedtapp.materials["vacuum"].conductivity.thermalmodifier = None + self.aedtapp.materials["vacuum"].conductivity.spatialmodifier = None diff --git a/_unittest/test_04_SBR.py b/_unittest/test_04_SBR.py index 7ef95e00347..9da95012550 100644 --- a/_unittest/test_04_SBR.py +++ b/_unittest/test_04_SBR.py @@ -24,7 +24,6 @@ import builtins import os -import sys from unittest.mock import mock_open from mock import patch @@ -254,7 +253,6 @@ def test_12_import_map(self): for part in parts_dict["parts"]: assert os.path.exists(parts_dict["parts"][part]["file_name"]) - @pytest.mark.skipif(sys.version_info > (3, 8), reason="Bug in VTK with 3.12") @pytest.mark.skipif(is_linux, reason="feature supported in Cpython") def test_16_read_hdm(self): self.aedtapp.insert_design("hdm") diff --git a/_unittest/test_08_Primitives3D.py b/_unittest/test_08_Primitives3D.py index f9955f51a19..9e02ec494ed 100644 --- a/_unittest/test_08_Primitives3D.py +++ b/_unittest/test_08_Primitives3D.py @@ -1460,6 +1460,7 @@ def test_71_create_choke(self, filename): self.aedtapp.delete_design(self.aedtapp.design_name) def test_72_check_choke_values(self): + self.aedtapp.insert_design("ChokeValues") choke_file1 = os.path.join(local_path, "example_models", "choke_json_file", "choke_1winding_1Layer.json") choke_file2 = os.path.join(local_path, "example_models", "choke_json_file", "choke_2winding_1Layer_Common.json") choke_file3 = os.path.join( diff --git a/_unittest/test_12_PostProcessing.py b/_unittest/test_12_PostProcessing.py index 0dfebc86637..18cf6a6de65 100644 --- a/_unittest/test_12_PostProcessing.py +++ b/_unittest/test_12_PostProcessing.py @@ -650,7 +650,6 @@ def test_70_far_field_data(self): ) assert isinstance(data_pyvista, Plotter) - @pytest.mark.skipif(sys.version_info > (3, 11), reason="Issues with VTK in Python 3.12") @pytest.mark.skipif(is_linux or sys.version_info < (3, 8), reason="FarFieldSolution not supported by IronPython") def test_71_antenna_plot(self, field_test): ffdata = field_test.get_antenna_ffd_solution_data(frequencies=30e9, sphere="3D") diff --git a/_unittest/test_27_Maxwell2D.py b/_unittest/test_27_Maxwell2D.py index 1c25f7594ac..28baf00ab85 100644 --- a/_unittest/test_27_Maxwell2D.py +++ b/_unittest/test_27_Maxwell2D.py @@ -596,6 +596,6 @@ def test_37_boundaries_by_type(self): def test_38_export_fields_calc(self): output_file = os.path.join(self.local_scratch.path, "e_tang_field.fld") assert self.m2d_field_export.post.export_field_file( - quantity="E_Line", output_dir=output_file, assignment="Poly1", objects_type="Line" + quantity="E_Line", output_file=output_file, assignment="Poly1", objects_type="Line" ) assert os.path.exists(output_file) diff --git a/_unittest/test_98_Icepak.py b/_unittest/test_98_Icepak.py index 514c3ebb178..203bc47562e 100644 --- a/_unittest/test_98_Icepak.py +++ b/_unittest/test_98_Icepak.py @@ -307,6 +307,9 @@ def test_05_EMLoss(self): "uUSB", "Setup1", "LastAdaptive", "2.5GHz", surface_list, HFSSpath, param_list, object_list ) + def test_06_clear_linked_data(self): + assert self.aedtapp.clear_linked_data() + def test_07_ExportStepForWB(self): file_path = self.local_scratch.path file_name = "WBStepModel" @@ -321,8 +324,8 @@ def test_08_Setup(self): assert self.aedtapp.assign_2way_coupling(setup_name, 2, True, 20) templates = SetupKeys().get_default_icepak_template(default_type="Natural Convection") assert templates - self.aedtapp.setups[0].props = templates["IcepakSteadyState"] - assert self.aedtapp.setups[0].update() + my_setup.props = templates["IcepakSteadyState"] + assert my_setup.update() assert SetupKeys().get_default_icepak_template(default_type="Default") assert SetupKeys().get_default_icepak_template(default_type="Forced Convection") with pytest.raises(AttributeError): @@ -1019,6 +1022,7 @@ def test_52_flatten_3d_components(self): self.aedtapp.delete_design() def test_53_create_conduting_plate(self): + self.aedtapp.insert_design("conducting") box = self.aedtapp.modeler.create_box([0, 0, 0], [10, 20, 10], name="box1") self.aedtapp.modeler.create_rectangle(self.aedtapp.PLANE.XY, [0, 0, 0], [10, 20], name="surf1") self.aedtapp.modeler.create_rectangle(self.aedtapp.PLANE.YZ, [0, 0, 0], [10, 20], name="surf2") diff --git a/_unittest_solvers/test_00_analyze.py b/_unittest_solvers/test_00_analyze.py index b1606b1e9a1..eec974cc0e1 100644 --- a/_unittest_solvers/test_00_analyze.py +++ b/_unittest_solvers/test_00_analyze.py @@ -192,11 +192,11 @@ def test_02_hfss_export_results(self, hfss_app): assert len(exported_files) > 0 fld_file1 = os.path.join(self.local_scratch.path, "test_fld_hfss1.fld") - assert hfss_app.post.export_field_file(quantity="Mag_E", output_dir=fld_file1, assignment="Box1", + assert hfss_app.post.export_field_file(quantity="Mag_E", output_file=fld_file1, assignment="Box1", intrinsics="1GHz", phase="5deg") assert os.path.exists(fld_file1) fld_file2 = os.path.join(self.local_scratch.path, "test_fld_hfss2.fld") - assert hfss_app.post.export_field_file(quantity="Mag_E", output_dir=fld_file2, assignment="Box1", + assert hfss_app.post.export_field_file(quantity="Mag_E", output_file=fld_file2, assignment="Box1", intrinsics="1GHz") assert os.path.exists(fld_file2) @@ -298,26 +298,26 @@ def test_03d_icepak_eval_tempc(self): def test_03e_icepak_ExportFLDFil(self): fld_file = os.path.join(self.local_scratch.path, "test_fld.fld") self.icepak_app.post.export_field_file(quantity="Temp", solution=self.icepak_app.nominal_sweep, variations={}, - output_dir=fld_file, assignment="box") + output_file=fld_file, assignment="box") assert os.path.exists(fld_file) fld_file_1 = os.path.join(self.local_scratch.path, "test_fld_1.fld") sample_points_file = os.path.join(local_path, "example_models", test_subfolder, "temp_points.pts") self.icepak_app.post.export_field_file(quantity="Temp", solution=self.icepak_app.nominal_sweep, variations=self.icepak_app.available_variations.nominal_w_values_dict, - output_dir=fld_file_1, assignment="box", + output_file=fld_file_1, assignment="box", sample_points_file=sample_points_file) assert os.path.exists(fld_file_1) fld_file_2 = os.path.join(self.local_scratch.path, "test_fld_2.fld") self.icepak_app.post.export_field_file(quantity="Temp", solution=self.icepak_app.nominal_sweep, variations=self.icepak_app.available_variations.nominal_w_values_dict, - output_dir=fld_file_2, assignment="box", + output_file=fld_file_2, assignment="box", sample_points=[[0, 0, 0], [3, 6, 8], [4, 7, 9]]) assert os.path.exists(fld_file_2) cs = self.icepak_app.modeler.create_coordinate_system() fld_file_3 = os.path.join(self.local_scratch.path, "test_fld_3.fld") self.icepak_app.post.export_field_file(quantity="Temp", solution=self.icepak_app.nominal_sweep, variations=self.icepak_app.available_variations.nominal_w_values_dict, - output_dir=fld_file_3, assignment="box", + output_file=fld_file_3, assignment="box", sample_points=[[0, 0, 0], [3, 6, 8], [4, 7, 9]], reference_coordinate_system=cs.name, export_in_si_system=False, export_field_in_reference=False) @@ -422,17 +422,17 @@ def test_07_export_maxwell_fields(self, m3dtransient): m3dtransient.analyze(m3dtransient.active_setup, cores=4, use_auto_settings=False) fld_file_3 = os.path.join(self.local_scratch.path, "test_fld_3.fld") assert m3dtransient.post.export_field_file(quantity="Mag_B", solution=m3dtransient.nominal_sweep, variations={}, - output_dir=fld_file_3, assignment="Coil_A2", objects_type="Surf", + output_file=fld_file_3, assignment="Coil_A2", objects_type="Surf", intrinsics="10ms") assert os.path.exists(fld_file_3) fld_file_4 = os.path.join(self.local_scratch.path, "test_fld_4.fld") assert not m3dtransient.post.export_field_file(quantity="Mag_B", solution=m3dtransient.nominal_sweep, variations=m3dtransient.available_variations.nominal_w_values_dict, - output_dir=fld_file_4, assignment="Coil_A2", + output_file=fld_file_4, assignment="Coil_A2", objects_type="invalid") setup = m3dtransient.setups[0] m3dtransient.setups[0].delete() - assert not m3dtransient.post.export_field_file(quantity="Mag_B", variations={}, output_dir=fld_file_4, + assert not m3dtransient.post.export_field_file(quantity="Mag_B", variations={}, output_file=fld_file_4, assignment="Coil_A2") new_setup = m3dtransient.create_setup(name=setup.name, setup_type=setup.setuptype) diff --git a/codecov.yml b/codecov.yml index 32adc7b2f60..d200f680ce7 100644 --- a/codecov.yml +++ b/codecov.yml @@ -25,3 +25,4 @@ coverage: - "pyaedt/common_rpc.py" # ignore folders and all its contents - "pyaedt/generic/grpc_plugin_dll_class.py" # ignore file to interact with AEDT grpc api - "pyaedt/edb.py" # ignore folders and all its contents + - "pyaedt/workflows/project/kernel_converter.py" # ignore folders and all its contents diff --git a/doc/source/Resources/pyaedt_installer_from_aedt.py b/doc/source/Resources/pyaedt_installer_from_aedt.py index 44f99ab280a..484a1458f53 100644 --- a/doc/source/Resources/pyaedt_installer_from_aedt.py +++ b/doc/source/Resources/pyaedt_installer_from_aedt.py @@ -193,15 +193,18 @@ def install_pyaedt(): if args.wheel and os.path.exists(args.wheel): wheel_pyaedt = args.wheel - import zipfile - unzipped_path = os.path.join(os.path.dirname(wheel_pyaedt), - os.path.splitext(os.path.basename(wheel_pyaedt))[0]) - if os.path.exists(unzipped_path): - shutil.rmtree(unzipped_path, ignore_errors=True) - with zipfile.ZipFile(wheel_pyaedt, 'r') as zip_ref: - # Extract all contents to a directory. (You can specify a different extraction path if needed.) - zip_ref.extractall(unzipped_path) - + if wheel_pyaedt.endswith(".zip"): + import zipfile + unzipped_path = os.path.join(os.path.dirname(wheel_pyaedt), + os.path.splitext(os.path.basename(wheel_pyaedt))[0]) + if os.path.exists(unzipped_path): + shutil.rmtree(unzipped_path, ignore_errors=True) + with zipfile.ZipFile(wheel_pyaedt, 'r') as zip_ref: + # Extract all contents to a directory. (You can specify a different extraction path if needed.) + zip_ref.extractall(unzipped_path) + else: + # Extracted folder. + unzipped_path = wheel_pyaedt if args.version <= "231": run_command( '"{}" install --no-cache-dir --no-index --find-links={} pyaedt[all,dotnet]'.format(pip_exe, diff --git a/doc/source/User_guide/extensions.rst b/doc/source/User_guide/extensions.rst index 9841c1e9f5d..dc85dfed5b2 100644 --- a/doc/source/User_guide/extensions.rst +++ b/doc/source/User_guide/extensions.rst @@ -34,6 +34,12 @@ Project extension apply to all extensions that are applicable for all AEDT appli Lear how to use the Advanced Fields Calculator. + .. grid-item-card:: Kernel converter + :link: pyaedt_extensions_doc/project/kernel_convert + :link-type: doc + + Lear how to convert projects from 2022R2 to newer versions. + .. toctree:: :hidden: diff --git a/doc/source/User_guide/pyaedt_extensions_doc/project/configure_edb.rst b/doc/source/User_guide/pyaedt_extensions_doc/project/configure_edb.rst index 758cae4baa7..0874b239676 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/project/configure_edb.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/project/configure_edb.rst @@ -12,7 +12,7 @@ The following image shows the extension user interface: The available arguments are: ``aedb_path``, ``configuration_path``. -User can pass as an argument a configuration file (a json formatted file or a toml file) or a folder containing more +User can pass as an argument a configuration file (a json formatted file or a toml file), or a folder containing more than N configuration files. In such case the script creates N new aedb projects, each one with corresponding setting file applied. @@ -22,10 +22,19 @@ setting file applied. :alt: Principle of working of Layout UI +A brief description of which options are defined in the configuration file: -You can also launch the extension user interface from the terminal. An example can be found here: - -.. toctree:: - :maxdepth: 2 - - ../commandline \ No newline at end of file +.. image:: ../../../_static/extensions/edb_config_setup.png + :width: 800 + :alt: Setup defined by a configuration file + +As depicted above, these options are importing a stackup, defining components and solderballs / bumps on them, +doing a cutout (much faster and easier than the UI one), +creating coaxial ports with an appropriate PEC backing, as well as, automatically creating distributed circuit ports (or current / voltage sources) on a component, +with the negative terminal of each being its nearest pin of the reference net. Moreover, a variety of simulation setups are supported, namely HFSS, SIwave SYZ, SIwave DC, +as well as, mesh operations that is length based. Last but not least, exporting a configuration file from the active design is also supported, hence the user can get the +configuration setup and re-use it with or without modifications as many times as possible. + +The value of this format and toolkit, lies in the fact that it is totally reusable, it is really user-friendly, even with users that are not familiar with scripting. +It supports most of the options that the UI also supports (not only the ones explained above, but many additional), and it has the advantage of obtaining the initial +configuration file from the design, by using its export property. diff --git a/doc/source/User_guide/pyaedt_extensions_doc/project/kernel_convert.rst b/doc/source/User_guide/pyaedt_extensions_doc/project/kernel_convert.rst new file mode 100644 index 00000000000..f75aac1ff33 --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/project/kernel_convert.rst @@ -0,0 +1,55 @@ +Kernel conversion +================= + +Conversion of the kernel from ACIS (2022R2 and previous versions), +to Parasolid (2023R1 and following versions) is becoming accessible to the customer, +with the use of a simple script. + +This script is compatible with .aedt files, as well as .a3dcomp files (including encrypted 3DComponents). + +You can access the extension from the icon created on the **Automation** tab using the Extension Manager. + +The following image shows the extension user interface: + +.. image:: ../../../_static/extensions/kernel_convert_ui.png + :width: 800 + :alt: Kernel Convert UI + +The Browse file or folder input points to a path which is either an .a3dcomp file, +an .aedt file, or a folder containing a variety of files of both types +(this way the user is capable to convert the kernel of a component library with one launch of the script) + +**Attention:** For encrypted 3D components the enable editing should be always on (with the corresponding +password that is given as an input). When pointing to a library, in order for +all the 3DComponents to be converted, they must have the same Application and Solution type, +given as an input in the last two entries of the UI, as well as same password, in order for the +conversion to be successful for all files. + +Last but least, for every file in the folder, a new file is generated in the path provided, that contains the +design converted to the latest version, and its name indicating the initial file version (i.e.test_aedt_2025.1) +Furthermore, for every conversion, a .csv file is created, with a name pointing to the converted design name, +containing any violations that occurred during the conversion, and that need **manual** fixing by the user. + +The following image show, the initial test_aedt and test_a3dcomp files, the corresponding .csv files and +the generated 2025R1 version (with the Parasolid Kernel) files. + +.. image:: ../../../_static/extensions/converted_files.png + :width: 800 + :alt: Generated Files During Kernel Conversion + +You can also launch the extension user interface from the terminal: + +.. code:: + + python.exe path/to/pyaedt/workflows/project/kernel_converter.py + +Finally, this code shows how you can run the extension directly from a Python script: + +.. code:: python + + from pyaedt.workflows.project.kernel_converter import main + + main(test_args = {"password": "my_pwd", + "application": "HFSS", + "solution": "Modal", + "file_path": "C:\my_path\file_containing_projects"}) diff --git a/doc/source/_static/extensions/converted_files.png b/doc/source/_static/extensions/converted_files.png new file mode 100644 index 00000000000..7c9b2a98413 Binary files /dev/null and b/doc/source/_static/extensions/converted_files.png differ diff --git a/doc/source/_static/extensions/edb_config_setup.png b/doc/source/_static/extensions/edb_config_setup.png new file mode 100644 index 00000000000..f07ded97677 Binary files /dev/null and b/doc/source/_static/extensions/edb_config_setup.png differ diff --git a/doc/source/_static/extensions/kernel_convert_ui.png b/doc/source/_static/extensions/kernel_convert_ui.png new file mode 100644 index 00000000000..7dd2cf987d3 Binary files /dev/null and b/doc/source/_static/extensions/kernel_convert_ui.png differ diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt index d60093abdb4..4125d4a4f2c 100644 --- a/doc/styles/config/vocabularies/ANSYS/accept.txt +++ b/doc/styles/config/vocabularies/ANSYS/accept.txt @@ -96,4 +96,7 @@ assign_mesh_region assign_mesh_level assign_mesh_reuse json -toml \ No newline at end of file +toml +Parasolid +i.e. +solderballs diff --git a/examples/06-Multiphysics/Circuit-HFSS-Icepak-coupling.py b/examples/06-Multiphysics/Circuit-HFSS-Icepak-coupling.py new file mode 100644 index 00000000000..1ea726ff417 --- /dev/null +++ b/examples/06-Multiphysics/Circuit-HFSS-Icepak-coupling.py @@ -0,0 +1,319 @@ +""" +Multiphysics: Circuit-HFSS-Icepak coupling workflow +--------------------------------------------------- +This example demonstrates how to create a two-way coupling between Circuit-HFSS designs and Icepak. + +Let's consider a design where some components are simulated in HFSS with a full 3D model, +while others are simulated in Circuit as lumped elements. The electrical simulation is done by +placing the HFSS design into a Circuit design as a subcomponent and by connecting the lumped components to +its ports. + +The purpose of the workflow is to perform a thermal simulation of the Circuit-HFSS design, +creating a two-way coupling with Icepak that allows running multiple iterations. +The losses from both designs are accounted for: EM losses are evaluated by the HFSS solver +and fed into Icepak via a direct link, while losses from the lumped components in the Circuit +design are evaluated analytically and must be manually set into the Icepak boundary. + +On the way back of the coupling, temperature information is handled differently for HFSS and Circuit. +For HFSS, a temperature map is exported from the Icepak design and used to create a 3D dataset; +then the material properties in the HFSS design are updated based on this dataset. +For Circuit, the average temperature of the lumped components is extracted from the Icepak design +and used to update the temperature-dependent characteristics of the lumped components in Circuit. + +In this example, the Circuit design contains only a resistor component, +with temperature-dependent resistance described by this formula: 0.162*(1+0.004*(TempE-TempE0)), +where TempE is the current temperature and TempE0 is the ambient temperature. +The HFSS design includes only a cylinder with temperature-dependent material conductivity, +defined by a 2D dataset. The resistor and the cylinder have matching resistances. + + +The workflow steps are as follows: + +1. Solve the HFSS design. +2. Refresh the dynamic link and solve the Circuit design. +3. Push excitations (HFSS design results are scaled automatically). +4. Extract the resistor's power loss value from the Circuit design. +5. Set the resistor's power loss value in the Icepak design (block thermal condition). +6. Solve the Icepak design. +7. Export the temperature map from the Icepak design and create a new 3D dataset with it. +8. Update material properties in the HFSS design based on the new dataset. +9. Extract the average temperature of the resistor from the Icepak design. +10. Update the resistance value in the Circuit design based on the new resistor average temperature. + +""" + +############################################################################### +# Perform required imports +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# Perform required imports. +from pyaedt import Circuit +from pyaedt import Hfss +from pyaedt import Icepak +from pyaedt import downloads +import os + + +############################################################################### +# Download and open project +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Download the project archive. Save it to the temporary folder. +project_path = downloads.download_file("circuit_hfss_icepak", "Circuit-HFSS-Icepak-workflow.aedtz") + +############################################################################### +# Open the project and get the Circuit design. +circuit = Circuit( + project=project_path, + new_desktop_session=True, + specified_version=241, + non_graphical=False +) + +############################################################################### +# Set the name of the resistor in Circuit. +resistor_body_name = "Circuit_Component" + +############################################################################### +# Set the name of the cylinder body in HFSS. +device3D_body_name = "Device_3D" + + +############################################################################### +# Get the Hfss design and prepare the material for the thermal link +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get the Hfss design. +hfss = Hfss(project=circuit.project_name) + +############################################################################### +# Create a new material that will be used to set the temperature map on it. +# The material is created by duplicating the material assigned to the cylinder. +material_name = hfss.modeler.objects_by_name[device3D_body_name].material_name +new_material_name = material_name + "_dataset" +new_material = hfss.materials.duplicate_material(material=material_name, name=new_material_name) + +############################################################################### +# Save the conductivity value. It will be used later in the iterations. +old_conductivity = new_material.conductivity.value + +############################################################################### +# Assign the new material to the cylinder object in HFSS. +hfss.modeler.objects_by_name[device3D_body_name].material_name = new_material_name + +############################################################################### +# Since this material has a high conductivity, HFSS automatically deactivate "Solve Inside". +# It needs to be set back on as we need to evaluate the losses inside the cylinder. +hfss.modeler.objects_by_name[device3D_body_name].solve_inside = True + + +############################################################################### +# Get the Icepak design +# ~~~~~~~~~~~~~~~~~~~~~ +# Get the Icepak design. +icepak = Icepak(project=circuit.project_name) + + +############################################################################### +# Set the parameters for the iterations +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Set the initial temperature to a value closer to the final one, to speed up the convergence. +circuit["TempE"] = "300cel" + +############################################################################### +# Set the maximum number of iterations. +max_iter = 5 + +############################################################################### +# Set the residual convergence criteria to stop the iterations. +temp_residual_limit = 0.02 +loss_residual_limit = 0.02 + +############################################################################### +# This variable will contain the iteration statistics. +stats = {} + + +############################################################################### +# Start the iterations +# ~~~~~~~~~~~~~~~~~~~~ +# Each for loop is a complete two-way iteration. +# The code is thoroughly commented. +# Please read the inline comments carefully for a full understanding. +for cp_iter in range(1, max_iter + 1): + stats[cp_iter] = {} + + + # Step 1: Solve the Hfss design + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Solve the Hfss design. + hfss.analyze() + + + # Step 2: Refresh the dynamic link and solve the Circuit design + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Find the HFSS subcomponent in Circuit. + # This information is required by refresh_dynamic_link and push_excitations methods. + hfss_component_name = "" + hfss_instance_name = "" + for component in circuit.modeler.components.components.values(): + if ( + component.model_name is not None + and hfss.design_name in component.model_name + ): + hfss_component_name = component.model_name + hfss_instance_name = component.refdes + break + if not hfss_component_name or not hfss_instance_name: + raise "Hfss component not found in Circuit design" + + # Refresh the dynamic link. + circuit.modeler.schematic.refresh_dynamic_link(name=hfss_component_name) + + # Solve the Circuit design. + circuit.analyze() + + + # Step 3: Push excitations (HFSS design results are scaled automatically) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Push excitations. + circuit.push_excitations(instance=hfss_instance_name) + + + # Step 4: Extract the resistor's power loss value from the Circuit design + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Evaluate power loss on resistor. + r_losses = circuit.post.get_solution_data(expressions="0.5*mag(I(I1)*V(V1))").data_magnitude()[0] + + # Save the losses in the stats. + stats[cp_iter]["losses"] = r_losses + + + # Step 5: Set the resistor's power loss value in the Icepak design (block thermal condition) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Find the Solid Block boundary in Icepak. + boundaries = icepak.boundaries + boundary = None + for bb in boundaries: + if bb.name == "Block1": + boundary = bb + break + if not boundary: + raise "Block boundary not defined in Icepak design." + + # Set the resistor's power loss in the Block Boundary. + boundary.props["Total Power"] = str(r_losses) + "W" + + + # Step 6: Solve the Icepak design + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Clear linked data, otherwise Icepak continues to run simulation with the initial losses. + icepak.clear_linked_data() + + # Solve the Icepak design. + icepak.analyze() + + + # Step 7: Export the temperature map from the Icepak and create a new 3D dataset with it + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Export the temperature map into a file. + fld_filename = os.path.join( + icepak.working_directory, f"temperature_map_{cp_iter}.fld" + ) + icepak.post.export_field_file( + quantity="Temp", output_file=fld_filename, assignment="AllObjects", objects_type="Vol" + ) + + # Convert the fld file format into a dataset tab file compatible with dataset import. + # The existing header lines must be removed and replaced with a single header line + # containing the value unit. + with open(fld_filename, "r") as f: + lines = f.readlines() + + _ = lines.pop(0) + _ = lines.pop(0) + lines.insert(0, '"X" "Y" "Z" "cel"\n') + + basename, _ = os.path.splitext(fld_filename) + tab_filename = basename + "_dataset.tab" + + with open(tab_filename, "w") as f: + f.writelines(lines) + + # Import the 3D dataset. + dataset_name = f"temp_map_step_{cp_iter}" + hfss.import_dataset3d( + input_file=tab_filename, name=dataset_name, is_project_dataset=True + ) + + + # Step 8: Update material properties in the HFSS design based on the new dataset + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Set the new conductivity value. + new_material.conductivity.value = ( + f"{old_conductivity}*Pwl($TempDepCond,clp(${dataset_name},X,Y,Z))" + ) + + # Switch off the thermal modifier of the material, if any. + new_material.conductivity.thermalmodifier = None + + + # Step 9: Extract the average temperature of the resistor from the Icepak design + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Get the mean temp value on the high resistivity object. + mean_temp = icepak.post.get_scalar_field_value( + quantity="Temp", scalar_function="Mean", object_name=resistor_body_name + ) + + # Save the temperature in the iteration stats. + stats[cp_iter]["temp"] = mean_temp + + + # Step 10: Update the resistance value in the Circuit design + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Set this temperature in circuit in variable TempE. + circuit["TempE"] = f"{mean_temp}cel" + + # Save the project + circuit.save_project() + + + # Check the convergence of the iteration + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Evaluate the relative residuals on temperature and losses. + # If the residuals are smaller than the threshold, set the convergence flag to `True`. + # Residuals are calculated starting from the second iteration. + converged = False + stats[cp_iter]["converged"] = converged + if cp_iter > 1: + delta_temp = abs(stats[cp_iter]["temp"] - stats[cp_iter-1]["temp"]) / abs(stats[cp_iter-1]["temp"]) + delta_losses = abs(stats[cp_iter]["losses"] - stats[cp_iter-1]["losses"]) / abs(stats[cp_iter-1]["losses"]) + if delta_temp <= temp_residual_limit and delta_losses <= loss_residual_limit: + converged = True + stats[cp_iter]["converged"] = converged + else: + delta_temp = None + delta_losses = None + + # Save the relative residuals in the iteration stats. + stats[cp_iter]["delta_temp"] = delta_temp + stats[cp_iter]["delta_losses"] = delta_losses + + # Exit from the loop if the convergence is reached. + if converged: + break + +############################################################################### +# Print the overall statistics +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Print the overall statistics for the multiphysic loop. +for i in stats: + txt = "yes" if stats[i]["converged"] else "no" + delta_temp = f"{stats[i]['delta_temp']:.4f}" if stats[i]['delta_temp'] is not None else "None" + delta_losses = f"{stats[i]['delta_losses']:.4f}" if stats[i]['delta_losses'] is not None else "None" + print( + f"Step {i}: temp={stats[i]['temp']:.3f}, losses={stats[i]['losses']:.3f}, " + f"delta_temp={delta_temp}, delta_losses={delta_losses}, " + f"converged={txt}" + ) + +############################################################################### +# Close Electronics Desktop +circuit.release_desktop() diff --git a/pyaedt/application/Analysis.py b/pyaedt/application/Analysis.py index 83001a3c655..4409de6c51f 100644 --- a/pyaedt/application/Analysis.py +++ b/pyaedt/application/Analysis.py @@ -217,7 +217,7 @@ def materials(self): Materials in the project. """ - if self._materials is None: + if self._materials is None and self._odesign: self.logger.reset_timer() from pyaedt.modules.MaterialLib import Materials @@ -281,7 +281,9 @@ def Position(self): Position object. """ - return self.modeler.Position + if self.modeler: + return self.modeler.Position + return @property def available_variations(self): diff --git a/pyaedt/application/Analysis3D.py b/pyaedt/application/Analysis3D.py index 435badc9708..b4d565c0b0a 100644 --- a/pyaedt/application/Analysis3D.py +++ b/pyaedt/application/Analysis3D.py @@ -148,7 +148,7 @@ def modeler(self): :class:`pyaedt.modeler.modeler3d.Modeler3D` or :class:`pyaedt.modeler.modeler2d.Modeler2D` Modeler object. """ - if self._modeler is None: + if self._modeler is None and self._odesign: self.logger.reset_timer() from pyaedt.modeler.modeler2d import Modeler2D @@ -167,7 +167,7 @@ def mesh(self): :class:`pyaedt.modules.Mesh.Mesh` or :class:`pyaedt.modules.MeshIcepak.IcepakMesh` Mesh object. """ - if self._mesh is None: + if self._mesh is None and self._odesign: self.logger.reset_timer() from pyaedt.modules.Mesh import Mesh @@ -187,7 +187,7 @@ def post(self): :class:`pyaedt.modules.AdvancedPostProcessing.PostProcessor` PostProcessor object. """ - if self._post is None: + if self._post is None and self._odesign: self.logger.reset_timer() if is_ironpython: # pragma: no cover from pyaedt.modules.PostProcessor import PostProcessor diff --git a/pyaedt/application/Analysis3DLayout.py b/pyaedt/application/Analysis3DLayout.py index fee24cda10e..918864a8afe 100644 --- a/pyaedt/application/Analysis3DLayout.py +++ b/pyaedt/application/Analysis3DLayout.py @@ -151,7 +151,7 @@ def post(self): :class:`pyaedt.modules.AdvancedPostProcessing.PostProcessor` PostProcessor object. """ - if self._post is None: + if self._post is None and self._odesign: self.logger.reset_timer() if is_ironpython: # pragma: no cover from pyaedt.modules.PostProcessor import PostProcessor @@ -170,7 +170,7 @@ def mesh(self): ------- :class:`pyaedt.modules.Mesh3DLayout.Mesh3d` """ - if self._mesh is None: + if self._mesh is None and self._odesign: from pyaedt.modules.Mesh3DLayout import Mesh3d self._mesh = Mesh3d(self) @@ -249,7 +249,7 @@ def modeler(self): ------- :class:`pyaedt.modeler.modelerpcb.Modeler3DLayout` """ - if self._modeler is None: + if self._modeler is None and self._odesign: self.logger.reset_timer() from pyaedt.modeler.modelerpcb import Modeler3DLayout diff --git a/pyaedt/application/AnalysisMaxwellCircuit.py b/pyaedt/application/AnalysisMaxwellCircuit.py index 8f34e2e9977..14eeea5aa6f 100644 --- a/pyaedt/application/AnalysisMaxwellCircuit.py +++ b/pyaedt/application/AnalysisMaxwellCircuit.py @@ -111,7 +111,7 @@ def modeler(self): ------- :class:`pyaedt.modeler.schematic.ModelerMaxwellCircuit` """ - if self._modeler is None: + if self._modeler is None and self._odesign: from pyaedt.modeler.schematic import ModelerMaxwellCircuit self._modeler = ModelerMaxwellCircuit(self) diff --git a/pyaedt/application/AnalysisNexxim.py b/pyaedt/application/AnalysisNexxim.py index 4ae54d9e5a9..c5ca77e4396 100644 --- a/pyaedt/application/AnalysisNexxim.py +++ b/pyaedt/application/AnalysisNexxim.py @@ -180,7 +180,7 @@ def post(self): :class:`pyaedt.modules.AdvancedPostProcessing.CircuitPostProcessor` PostProcessor object. """ - if self._post is None: + if self._post is None and self._odesign: self.logger.reset_timer() from pyaedt.modules.PostProcessor import CircuitPostProcessor @@ -221,7 +221,7 @@ def nominal_sweep(self): @property def modeler(self): """Modeler object.""" - if self._modeler is None: + if self._modeler is None and self._odesign: self.logger.reset_timer() from pyaedt.modeler.schematic import ModelerNexxim diff --git a/pyaedt/application/AnalysisRMxprt.py b/pyaedt/application/AnalysisRMxprt.py index 4989ecc2ef7..3fcde1c3e53 100644 --- a/pyaedt/application/AnalysisRMxprt.py +++ b/pyaedt/application/AnalysisRMxprt.py @@ -89,7 +89,7 @@ def post(self): ------- :class:`pyaedt.modules.PostProcessor.CircuitPostProcessor` """ - if self._post is None: # pragma: no cover + if self._post is None and self._odesign: # pragma: no cover self.logger.reset_timer() from pyaedt.modules.PostProcessor import CircuitPostProcessor @@ -107,7 +107,7 @@ def modeler(self): :class:`pyaedt.modules.modeler2d.ModelerRMxprt` """ - if self._modeler is None: + if self._modeler is None and self._odesign: from pyaedt.modeler.modeler2d import ModelerRMxprt self._modeler = ModelerRMxprt(self) diff --git a/pyaedt/application/AnalysisTwinBuilder.py b/pyaedt/application/AnalysisTwinBuilder.py index 1acabd68878..a071cababe4 100644 --- a/pyaedt/application/AnalysisTwinBuilder.py +++ b/pyaedt/application/AnalysisTwinBuilder.py @@ -123,7 +123,7 @@ def modeler(self): ------- :class:`pyaedt.modeler.schematic.ModelerTwinBuilder` """ - if self._modeler is None: + if self._modeler is None and self._odesign: from pyaedt.modeler.schematic import ModelerTwinBuilder self._modeler = ModelerTwinBuilder(self) @@ -137,7 +137,7 @@ def post(self): ------- :class:`pyaedt.modules.PostProcessor.CircuitPostProcessor` """ - if self._post is None: # pragma: no cover + if self._post is None and self._odesign: # pragma: no cover self.logger.reset_timer() from pyaedt.modules.PostProcessor import CircuitPostProcessor diff --git a/pyaedt/application/Design.py b/pyaedt/application/Design.py index 068d7ed51ea..466c1f2b24b 100644 --- a/pyaedt/application/Design.py +++ b/pyaedt/application/Design.py @@ -1085,8 +1085,7 @@ def odesign(self): Returns ------- - type - Design object. + Design object References ---------- @@ -3266,9 +3265,12 @@ def close_project(self, name=None, save=True): self._init_variables() self._oproject = None self._odesign = None + self.logger.odesign = None + self.logger.oproject = None + self.design_solutions._odesign = None + AedtObjects.__init__(self, self._desktop_class, is_inherithed=True) else: self.desktop_class.active_project(legacy_name) - AedtObjects.__init__(self, self._desktop_class, is_inherithed=True) i = 0 timeout = 10 @@ -3324,11 +3326,17 @@ def delete_design(self, name=None, fallback_design=None): if is_windows: self._init_variables() self._odesign = None + self.logger.odesign = None + self.design_solutions._odesign = None + AedtObjects.__init__(self, self._desktop_class, project=self.oproject, is_inherithed=True) return False else: if is_windows: self._init_variables() self._odesign = None + self.logger.odesign = None + self.design_solutions._odesign = None + AedtObjects.__init__(self, self._desktop_class, project=self.oproject, is_inherithed=True) return True @pyaedt_function_handler(separator_name="name") @@ -4168,11 +4176,15 @@ class DesignSettings: def __init__(self, app): self._app = app self.manipulate_inputs = None + + @property + def design_settings(self): + """Design settings.""" try: - self.design_settings = self._app.odesign.GetChildObject("Design Settings") + return self._app.odesign.GetChildObject("Design Settings") except GrpcApiError: # pragma: no cover self._app.logger.error("Failed to retrieve design settings.") - self.design_settings = None + return None @property def available_properties(self): diff --git a/pyaedt/application/aedt_objects.py b/pyaedt/application/aedt_objects.py index eb26dfef1bd..636ba6b911e 100644 --- a/pyaedt/application/aedt_objects.py +++ b/pyaedt/application/aedt_objects.py @@ -33,6 +33,8 @@ class AedtObjects(object): def __init__(self, desktop=None, project=None, design=None, is_inherithed=False): + self._odesign = design + self._oproject = project if desktop: self._odesktop = desktop.odesktop elif _desktop_sessions and project: @@ -48,15 +50,14 @@ def __init__(self, desktop=None, project=None, design=None, is_inherithed=False) if not is_inherithed: if project: - self.oproject = project if design: - self.odesign = design + self._odesign = design else: - self.odesign = self.oproject.GetActiveDesign() + self._odesign = self._oproject.GetActiveDesign() else: - self.oproject = self._odesktop.GetActiveProject() - if self.oproject: - self.odesign = self.oproject.GetActiveDesign() + self._oproject = self._odesktop.GetActiveProject() + if self._oproject: + self._odesign = self._oproject.GetActiveDesign() self._oboundary = None self._oimport_export = None self._ooptimetrics = None @@ -90,16 +91,16 @@ def oradfield(self): >>> oDesign.GetModule("RadField") """ - if self.design_type == "HFSS" and self.odesign.GetSolutionType() not in ["EigenMode", "Characteristic Mode"]: - return self.odesign.GetModule("RadField") + if self.design_type == "HFSS" and self._odesign.GetSolutionType() not in ["EigenMode", "Characteristic Mode"]: + return self._odesign.GetModule("RadField") return None @pyaedt_function_handler() def get_module(self, module_name): """Aedt Module object.""" - if self.design_type not in ["EMIT"] and self.odesign: + if self.design_type not in ["EMIT"] and self._odesign: try: - return self.odesign.GetModule(module_name) + return self._odesign.GetModule(module_name) except Exception: return None return None @@ -113,7 +114,9 @@ def o_symbol_manager(self): >>> oSymbolManager = oDefinitionManager.GetManager("Symbol") """ - return self.odefinition_manager.GetManager("Symbol") + if self.odefinition_manager: + return self.odefinition_manager.GetManager("Symbol") + return @property def opadstackmanager(self): @@ -124,13 +127,13 @@ def opadstackmanager(self): >>> oPadstackManger = oDefinitionManager.GetManager("Padstack") """ - if not self._opadstackmanager: - self._opadstackmanager = self.oproject.GetDefinitionManager().GetManager("Padstack") + if self._oproject and not self._opadstackmanager: + self._opadstackmanager = self._oproject.GetDefinitionManager().GetManager("Padstack") return self._opadstackmanager @property def design_type(self): - return self.odesign.GetDesignType() + return self._odesign.GetDesignType() @property def oboundary(self): @@ -215,8 +218,8 @@ def odefinition_manager(self): >>> oDefinitionManager = oProject.GetDefinitionManager() """ - if not self._odefinition_manager: - self._odefinition_manager = self.oproject.GetDefinitionManager() + if not self._odefinition_manager and self._oproject: + self._odefinition_manager = self._oproject.GetDefinitionManager() return self._odefinition_manager @property @@ -228,9 +231,8 @@ def omaterial_manager(self): >>> oMaterialManager = oDefinitionManager.GetManager("Material") """ - if not self._omaterial_manager: - if self.odefinition_manager: - self._omaterial_manager = self.odefinition_manager.GetManager("Material") + if self.odefinition_manager and not self._omaterial_manager: + self._omaterial_manager = self.odefinition_manager.GetManager("Material") return self._omaterial_manager @property @@ -247,7 +249,7 @@ def omodelsetup(self): if not self._omodel_setup: if ( self.design_type in ["Maxwell 3D", "Maxwell 2D"] - and self.odesign.GetSolutionType() == "Transient" + and self._odesign.GetSolutionType() == "Transient" or self.design_type == "HFSS" ): self._omodel_setup = self.get_module("ModelSetup") @@ -262,7 +264,7 @@ def o_maxwell_parameters(self): >>> oDesign.GetModule("MaxwellParameterSetup") """ - if self.design_type not in ["Maxwell 3D", "Maxwell 2D"]: + if self._odesign and self.design_type not in ["Maxwell 3D", "Maxwell 2D"]: return if not self._o_maxwell_parameters: self._o_maxwell_parameters = self.get_module("MaxwellParameterSetup") @@ -271,7 +273,7 @@ def o_maxwell_parameters(self): @property def omonitor(self): """AEDT Monitor Object.""" - if not self.design_type == "Icepak": + if not self._odesign or not self.design_type == "Icepak": return if not self._omonitor: self._omonitor = self.get_module("Monitor") @@ -395,18 +397,18 @@ def oeditor(self): ---------- >>> oEditor = oDesign.SetActiveEditor("SchematicEditor")""" - if not self._oeditor: + if not self._oeditor and self._odesign: if self.design_type in ["Circuit Design", "Twin Builder", "Maxwell Circuit", "EMIT"]: - self._oeditor = self.odesign.SetActiveEditor("SchematicEditor") + self._oeditor = self._odesign.SetActiveEditor("SchematicEditor") if is_linux and settings.aedt_version == "2024.1": time.sleep(1) self._odesktop.CloseAllWindows() elif self.design_type in ["HFSS 3D Layout Design", "HFSS3DLayout"]: - self._oeditor = self.odesign.SetActiveEditor("Layout") + self._oeditor = self._odesign.SetActiveEditor("Layout") elif self.design_type in ["RMxprt", "RMxprtSolution"]: - self._oeditor = self.odesign.SetActiveEditor("Machine") + self._oeditor = self._odesign.SetActiveEditor("Machine") else: - self._oeditor = self.odesign.SetActiveEditor("3D Modeler") + self._oeditor = self._odesign.SetActiveEditor("3D Modeler") return self._oeditor @property @@ -419,19 +421,19 @@ def layouteditor(self): >>> oDesign.SetActiveEditor("Layout") """ if not self._layouteditor and self.design_type in ["Circuit Design"]: - self._layouteditor = self.odesign.SetActiveEditor("Layout") + self._layouteditor = self._odesign.SetActiveEditor("Layout") return self._layouteditor @property def o_component_manager(self): """Component manager object.""" - if not self._o_component_manager: + if not self._o_component_manager and self.odefinition_manager: self._o_component_manager = self.odefinition_manager.GetManager("Component") return self._o_component_manager @property def o_model_manager(self): """Model manager object.""" - if not self._o_model_manager: + if not self._o_model_manager and self.odefinition_manager: self._o_model_manager = self.odefinition_manager.GetManager("Model") return self._o_model_manager diff --git a/pyaedt/generic/configurations.py b/pyaedt/generic/configurations.py index 351d0aa67cd..ff8bf97e1c6 100644 --- a/pyaedt/generic/configurations.py +++ b/pyaedt/generic/configurations.py @@ -1700,22 +1700,27 @@ def _export_mesh_operations(self, dict_out): dict_out["mesh"]["Settings"] = mop["Settings"] if self._app.mesh.meshregions: for mesh in self._app.mesh.meshregions: - if mesh.name == "Settings": + if mesh.name in ["Settings", "Global"]: args = ["NAME:Settings"] else: args = ["NAME:" + mesh.name, "Enable:=", mesh.Enable] args += mesh.settings.parse_settings_as_args() - args += getattr(mesh, "_parse_assignment_value")() + if mesh.name not in ["Settings", "Global"]: + args += getattr(mesh, "_parse_assignment_value")() args += ["UserSpecifiedSettings:=", not mesh.manual_settings] mop = OrderedDict({}) _arg2dict(args, mop) - if self._app.modeler[args[-3][0]].history().command == "CreateSubRegion": + if ( + mesh.name not in ["Settings", "Global"] + and self._app.modeler[args[-3][0]].history().command == "CreateSubRegion" + ): mop[mesh.name]["_subregion_information"] = { "pad_vals": mesh.assignment.padding_values, "pad_types": mesh.assignment.padding_types, "parts": list(mesh.assignment.parts.keys()), } - dict_out["mesh"][mesh.name] = mop[mesh.name] + if mesh.name in mop: + dict_out["mesh"][mesh.name] = mop[mesh.name] self._map_object(mop, dict_out) @pyaedt_function_handler() diff --git a/pyaedt/generic/plot.py b/pyaedt/generic/plot.py index b5aa99ba256..f8b28e7c477 100644 --- a/pyaedt/generic/plot.py +++ b/pyaedt/generic/plot.py @@ -1737,13 +1737,13 @@ def s_callback(): # pragma: no cover supported_export = [".svg", ".pdf", ".eps", ".ps", ".tex"] extension = os.path.splitext(export_image_path)[1] if extension in supported_export: - self.pv.save_graphic(export_image_path, raster=raster, painter=painter) + self.pv.save_graphic(export_image_path) else: - self.pv.show(screenshot=export_image_path, full_screen=True) + self.pv.show(auto_close=False, screenshot=export_image_path, full_screen=True) elif show and self.is_notebook: # pragma: no cover - self.pv.show() # pragma: no cover + self.pv.show(auto_close=False) # pragma: no cover elif show: - self.pv.show(full_screen=True) # pragma: no cover + self.pv.show(auto_close=False, full_screen=True) # pragma: no cover self.image_file = export_image_path return True @@ -1910,7 +1910,7 @@ def p_callback(): else: self.pv.isometric_view() self.pv.camera.zoom(self.zoom) - cpos = self.pv.show(interactive=False, auto_close=False, interactive_update=not self.off_screen) + self.pv.show(interactive=False, auto_close=False, interactive_update=not self.off_screen) start = time.time() try: diff --git a/pyaedt/icepak.py b/pyaedt/icepak.py index 375c106e602..2c6cfb36176 100644 --- a/pyaedt/icepak.py +++ b/pyaedt/icepak.py @@ -6464,6 +6464,28 @@ def create_square_wave_transient_assignment(self, on_value, initial_time_off, on """ return SquareWaveDictionary(on_value, initial_time_off, on_time, off_time, off_value) + @pyaedt_function_handler() + def clear_linked_data(self): + """ + Clear the linked data of all the solution setups. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + + >>> oDesign.ClearLinkedData + """ + try: + self.odesign.ClearLinkedData() + except: + return False + else: + return True + class IcepakDesignSettingsManipulation(DesignSettingsManipulation): def __init__(self, app): diff --git a/pyaedt/modules/Material.py b/pyaedt/modules/Material.py index 7214ce71392..f656dca9c72 100644 --- a/pyaedt/modules/Material.py +++ b/pyaedt/modules/Material.py @@ -460,11 +460,20 @@ def thermalmodifier(self, thermal_value): >>> oDefinitionManager.EditMaterial """ - if isinstance(thermal_value, str): + if thermal_value is None: + self._property_value[0].thermalmodifier = None + self._reset_thermal_modifier() + elif isinstance(thermal_value, str): + self._property_value[0].thermalmodifier = thermal_value self._add_thermal_modifier(thermal_value, 0) else: - for i in thermal_value: - self._add_thermal_modifier(i, thermal_value.index(i)) + for i, value in enumerate(thermal_value): + self._add_thermal_modifier(value, i) + try: + self._property_value[i].thermalmodifier = value + except IndexError: + self._property_value.append(BasicValue()) + self._property_value[i].thermalmodifier = value def _add_thermal_modifier(self, formula, index): """Add a thermal modifier. @@ -484,6 +493,10 @@ def _add_thermal_modifier(self, formula, index): if ( "ModifierData" not in self._material._props or "ThermalModifierData" not in self._material._props["ModifierData"] + or ( + "all_thermal_modifiers" in self._material._props["ModifierData"]["ThermalModifierData"] + and bool(self._material._props["ModifierData"]["ThermalModifierData"]["all_thermal_modifiers"]) == False + ) ): tm = OrderedDict( { @@ -602,6 +615,35 @@ def _add_thermal_modifier(self, formula, index): ].append(tm) return self._material.update() + def _reset_thermal_modifier(self): + """Set the thermal modifier to None. + + Returns + ------- + type + + """ + if "ModifierData" in self._material._props and "ThermalModifierData" in self._material._props["ModifierData"]: + if ( + "ModifierData" in self._material._props + and "SpatialModifierData" in self._material._props["ModifierData"] + ): + self._material._props["ModifierData"] = { + "SpatialModifierData": self._material._props["ModifierData"]["SpatialModifierData"], + "ThermalModifierData": { + "modifier_data": "thermal_modifier_data", + "all_thermal_modifiers": None, + }, + } + else: + self._material._props["ModifierData"] = { + "ThermalModifierData": { + "modifier_data": "thermal_modifier_data", + "all_thermal_modifiers": None, + } + } + return self._material.update() + @pyaedt_function_handler() def add_thermal_modifier_free_form(self, formula, index=0): """Add a thermal modifier to a material property using a free-form formula. @@ -773,6 +815,10 @@ def add_thermal_modifier_closed_form( if ( "ModifierData" not in self._material._props or "ThermalModifierData" not in self._material._props["ModifierData"] + or ( + "all_thermal_modifiers" in self._material._props["ModifierData"]["ThermalModifierData"] + and bool(self._material._props["ModifierData"]["ThermalModifierData"]["all_thermal_modifiers"]) == False + ) ): if ( "ModifierData" in self._material._props @@ -934,11 +980,20 @@ def spatialmodifier(self, spatial_value): >>> oDefinitionManager.EditMaterial """ - if isinstance(spatial_value, str): + if spatial_value is None: + self._property_value[0].spatialmodifier = None + self._reset_spatial_modifier() + elif isinstance(spatial_value, str): + self._property_value[0].spatialmodifier = spatial_value self._add_spatial_modifier(spatial_value, 0) else: - for i in spatial_value: - self._add_spatial_modifier(i, spatial_value.index(i)) + for i, value in enumerate(spatial_value): + self._add_spatial_modifier(value, i) + try: + self._property_value[i].spatialmodifier = value + except IndexError: + self._property_value.append(BasicValue()) + self._property_value[i].spatialmodifier = value def _add_spatial_modifier(self, formula, index): """Add a spatial modifier. @@ -958,6 +1013,10 @@ def _add_spatial_modifier(self, formula, index): if ( "ModifierData" not in self._material._props or "SpatialModifierData" not in self._material._props["ModifierData"] + or ( + "all_spatial_modifiers" in self._material._props["ModifierData"]["SpatialModifierData"] + and bool(self._material._props["ModifierData"]["SpatialModifierData"]["all_spatial_modifiers"]) == False + ) ): sm = OrderedDict( { @@ -1049,6 +1108,35 @@ def _add_spatial_modifier(self, formula, index): ].append(sm) return self._material.update() + def _reset_spatial_modifier(self): + """Set the spatial modifier to None. + + Returns + ------- + type + + """ + if "ModifierData" in self._material._props and "SpatialModifierData" in self._material._props["ModifierData"]: + if ( + "ModifierData" in self._material._props + and "ThermalModifierData" in self._material._props["ModifierData"] + ): + self._material._props["ModifierData"] = { + "ThermalModifierData": self._material._props["ModifierData"]["ThermalModifierData"], + "SpatialModifierData": { + "modifier_data": "spatial_modifier_data", + "all_spatial_modifiers": None, + }, + } + else: + self._material._props["ModifierData"] = { + "SpatialModifierData": { + "modifier_data": "spatial_modifier_data", + "all_spatial_modifiers": None, + } + } + return self._material.update() + @pyaedt_function_handler() def add_spatial_modifier_free_form(self, formula, index=0): """Add a spatial modifier to a material property using a free-form formula. diff --git a/pyaedt/modules/MeshIcepak.py b/pyaedt/modules/MeshIcepak.py index 832b0e5741c..fe278b64c75 100644 --- a/pyaedt/modules/MeshIcepak.py +++ b/pyaedt/modules/MeshIcepak.py @@ -279,11 +279,12 @@ def object(self): ::class::modeler.cad.object3d.Object3d """ if isinstance(self, Region): - return { - "CreateRegion": oo - for o, oo in self._app.modeler.objects_by_name.items() - if oo.history() and oo.history().command == "CreateRegion" - }.get("CreateRegion", None) + # use native apis instead of history() for performance reasons + for o, oo in self._app.modeler.objects_by_name.items(): + child_names = self._app.oeditor.GetChildObject(o).GetChildNames() + if child_names and child_names[0].startswith("CreateRegion"): + return oo + return None else: return self._app.modeler.objects_by_name.get(self._name, None) @@ -1089,30 +1090,31 @@ def boundingdimension(self): def _get_design_mesh_operations(self): """Retrieve design mesh operations.""" meshops = [] + dp = self._app.design_properties try: if settings.aedt_version > "2023.2": - for ds in self._app.design_properties["MeshRegion"]["MeshSetup"]: - if isinstance(self._app.design_properties["MeshRegion"]["MeshSetup"][ds], (OrderedDict, dict)): - if self._app.design_properties["MeshRegion"]["MeshSetup"][ds]["DType"] == "OpT": + for ds in dp["MeshRegion"]["MeshSetup"]: + if isinstance(dp["MeshRegion"]["MeshSetup"][ds], (OrderedDict, dict)): + if dp["MeshRegion"]["MeshSetup"][ds]["DType"] == "OpT": meshops.append( MeshOperation( self, ds, - self._app.design_properties["MeshRegion"]["MeshSetup"][ds], + dp["MeshRegion"]["MeshSetup"][ds], "Icepak", ) ) - else: - for ds in self._app.design_properties["MeshRegion"]["MeshSetup"]["MeshOperations"]: + else: # pragma: no cover + for ds in dp["MeshRegion"]["MeshSetup"]["MeshOperations"]: if isinstance( - self._app.design_properties["MeshRegion"]["MeshSetup"]["MeshOperations"][ds], + dp["MeshRegion"]["MeshSetup"]["MeshOperations"][ds], (OrderedDict, dict), ): meshops.append( MeshOperation( self, ds, - self._app.design_properties["MeshRegion"]["MeshSetup"]["MeshOperations"][ds], + dp["MeshRegion"]["MeshSetup"]["MeshOperations"][ds], "Icepak", ) ) @@ -1130,12 +1132,13 @@ def _get_design_mesh_operations(self): def _get_design_mesh_regions(self): """Retrieve design mesh regions.""" meshops = [] + dp = self._app.design_properties try: if settings.aedt_version > "2023.2": - for ds in self._app.design_properties["MeshRegion"]["MeshSetup"]: - if isinstance(self._app.design_properties["MeshRegion"]["MeshSetup"][ds], (OrderedDict, dict)): - if self._app.design_properties["MeshRegion"]["MeshSetup"][ds]["DType"] == "RegionT": - dict_prop = self._app.design_properties["MeshRegion"]["MeshSetup"][ds] + for ds in dp["MeshRegion"]["MeshSetup"]: + if isinstance(dp["MeshRegion"]["MeshSetup"][ds], (OrderedDict, dict)): + if dp["MeshRegion"]["MeshSetup"][ds]["DType"] == "RegionT": + dict_prop = dp["MeshRegion"]["MeshSetup"][ds] if ds == "Global": meshop = GlobalMeshRegion(self._app) else: @@ -1144,12 +1147,10 @@ def _get_design_mesh_regions(self): if el in meshop.__dict__: meshop.__dict__[el] = dict_prop[el] meshops.append(meshop) - else: - for ds in self._app.design_properties["MeshRegion"]["MeshSetup"]["MeshRegions"]: - if isinstance( - self._app.design_properties["MeshRegion"]["MeshSetup"]["MeshRegions"][ds], (OrderedDict, dict) - ): - dict_prop = self._app.design_properties["MeshRegion"]["MeshSetup"]["MeshRegions"][ds] + else: # pragma: no cover + for ds in dp["MeshRegion"]["MeshSetup"]["MeshRegions"]: + if isinstance(dp["MeshRegion"]["MeshSetup"]["MeshRegions"][ds], (OrderedDict, dict)): + dict_prop = dp["MeshRegion"]["MeshSetup"]["MeshRegions"][ds] if ds == "Global": meshop = GlobalMeshRegion(self._app) else: @@ -1359,13 +1360,13 @@ def assign_priorities(self, assignment): if not assignment or not isinstance(assignment, list) or not isinstance(assignment[0], list): raise AttributeError("``assignment`` input must be a list of lists.") props = {"PriorityListParameters": []} + self._app.logger.info("Parsing input objects information for priority assignment. This operation can take time") + udc = self.modeler.user_defined_components + udc._parse_objs() for level, objects in enumerate(assignment): level += 1 if isinstance(objects[0], str): - objects = [ - self.modeler.objects_by_name.get(o, self.modeler.user_defined_components.get(o, None)) - for o in objects - ] + objects = [self.modeler.objects_by_name.get(o, udc.get(o, None)) for o in objects] obj_3d = [ o for o in objects @@ -1405,6 +1406,7 @@ def assign_priorities(self, assignment): raise AttributeError("Cannot assign components and parts on the same level.") props["PriorityListParameters"].append(level_2d) props = {"UpdatePriorityListData": props} + self._app.logger.info("Input objects information for priority assignment completed.") args = [] _dict2arg(props, args) self.modeler.oeditor.UpdatePriorityList(args[0]) diff --git a/pyaedt/modules/PostProcessor.py b/pyaedt/modules/PostProcessor.py index e32a6179530..dba153fa0cc 100644 --- a/pyaedt/modules/PostProcessor.py +++ b/pyaedt/modules/PostProcessor.py @@ -2907,7 +2907,7 @@ def export_field_file_on_grid( @pyaedt_function_handler( quantity_name="quantity", variation_dict="variations", - filename="output_dir", + filename="output_file", obj_list="assignment", obj_type="objects_type", sample_points_lists="sample_points", @@ -2917,7 +2917,7 @@ def export_field_file( quantity, solution=None, variations=None, - output_dir=None, + output_file=None, assignment="AllObjects", objects_type="Vol", intrinsics=None, @@ -2941,9 +2941,9 @@ def export_field_file( variations : dict, optional Dictionary of all variation variables with their values. The default is ``None``. - output_dir : str, optional + output_file : str, optional Full path and name to save the file to. - The default is ``None`` which export file in working_directory. + The default is ``None`` which export a file named ``".fld"`` in working_directory. assignment : str, optional List of objects to export. The default is ``"AllObjects"``. objects_type : str, optional @@ -2994,12 +2994,12 @@ def export_field_file( self.logger.error("There are no existing sweeps.") return False solution = self._app.existing_analysis_sweeps[0] - if not output_dir: + if not output_file: appendix = "" ext = ".fld" - output_dir = os.path.join(self._app.working_directory, solution.replace(" : ", "_") + appendix + ext) + output_file = os.path.join(self._app.working_directory, solution.replace(" : ", "_") + appendix + ext) else: - output_dir = output_dir.replace("//", "/").replace("\\", "/") + output_file = output_file.replace("//", "/").replace("\\", "/") self.ofieldsreporter.CalcStack("clear") try: self.ofieldsreporter.EnterQty(quantity) @@ -3042,7 +3042,7 @@ def export_field_file( args = ["Solution:=", solution, "Geometry:=", assignment, "GeometryType:=", objects_type] else: args = ["Solution:=", solution] - self.ofieldsreporter.CalculatorWrite(output_dir, args, variation) + self.ofieldsreporter.CalculatorWrite(output_file, args, variation) elif sample_points_file: export_options = [ "NAME:ExportOption", @@ -3056,7 +3056,7 @@ def export_field_file( export_field_in_reference, ] self.ofieldsreporter.ExportToFile( - output_dir, + output_file, sample_points_file, solution, variation, @@ -3080,15 +3080,15 @@ def export_field_file( export_field_in_reference, ] self.ofieldsreporter.ExportToFile( - output_dir, + output_file, sample_points_file, solution, variation, export_options, ) - if os.path.exists(output_dir): - return output_dir + if os.path.exists(output_file): + return output_file return False # pragma: no cover @pyaedt_function_handler(plotname="plot_name", filepath="output_dir", filename="file_name") diff --git a/pyaedt/modules/SolveSetup.py b/pyaedt/modules/SolveSetup.py index c153da6b08a..e9b1e5bd56b 100644 --- a/pyaedt/modules/SolveSetup.py +++ b/pyaedt/modules/SolveSetup.py @@ -2032,11 +2032,11 @@ def _get_primitives_points_per_net(self): continue z = layers_elevation[prim.layer_name] if "EdbPath" in str(prim): - points = list(prim.center_line.Points) - pt = [points[0].X.ToDouble(), points[0].Y.ToDouble()] + points = list(prim.center_line) + pt = [points[0][0], points[0][1]] pt.append(z) next_p = int(len(points) / 4) - pt = [points[next_p].X.ToDouble(), points[next_p].Y.ToDouble()] + pt = [points[next_p][0], points[next_p][1]] pt.append(z) primitive_dict[net].append(pt) diff --git a/pyaedt/modules/monitor_icepak.py b/pyaedt/modules/monitor_icepak.py index aa6b933696b..21cd1e2f564 100644 --- a/pyaedt/modules/monitor_icepak.py +++ b/pyaedt/modules/monitor_icepak.py @@ -98,7 +98,6 @@ def __init__(self, p_app): self.quantities_dict = quantities_dict_2 else: self.quantities_dict = quantities_dict_1 - self._omonitor = self._app.odesign.GetModule("Monitor") if self._app.design_properties: # if is not a 3d comp/blank file aedtfile_monitor_dict = self._app.design_properties["Monitor"]["IcepakMonitors"].copy() del aedtfile_monitor_dict["NextUniqueID"] @@ -106,6 +105,10 @@ def __init__(self, p_app): if aedtfile_monitor_dict: self._load_monitor_objects(aedtfile_monitor_dict) + @property + def _omonitor(self): + return self._app.omonitor + @pyaedt_function_handler def _find_point(self, position): for point in self._app.oeditor.GetPoints(): diff --git a/pyaedt/modules/solutions.py b/pyaedt/modules/solutions.py index f7e39abc318..a98e14b02b0 100644 --- a/pyaedt/modules/solutions.py +++ b/pyaedt/modules/solutions.py @@ -2592,7 +2592,7 @@ def scale(value=1): if image_path: p.show(screenshot=image_path) if show: # pragma: no cover - p.show() + p.show(auto_close=False) return p @pyaedt_function_handler() diff --git a/pyaedt/sbrplus/plot.py b/pyaedt/sbrplus/plot.py index 87675122497..aac7582bac6 100644 --- a/pyaedt/sbrplus/plot.py +++ b/pyaedt/sbrplus/plot.py @@ -166,7 +166,7 @@ def plot_rays(self, snapshot_path=None): if snapshot_path: self.pv.show(screenshot=snapshot_path, full_screen=True) else: - self.pv.show() + self.pv.show(auto_close=False) return self.pv @pyaedt_function_handler() @@ -222,7 +222,7 @@ def plot_first_bounce_currents(self, snapshot_path=None): if snapshot_path: self.pv.show(screenshot=snapshot_path, full_screen=True) else: - self.pv.show() + self.pv.show(auto_close=False) @pyaedt_function_handler() def _add_objects(self): diff --git a/pyaedt/workflows/project/kernel_converter.py b/pyaedt/workflows/project/kernel_converter.py new file mode 100644 index 00000000000..85553458444 --- /dev/null +++ b/pyaedt/workflows/project/kernel_converter.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +import logging +import os.path + +from pyaedt import Desktop +from pyaedt import Hfss +from pyaedt import Icepak +from pyaedt import Maxwell3d +from pyaedt import Q3d +from pyaedt import settings +from pyaedt.application.design_solutions import solutions_types +from pyaedt.generic.design_types import get_pyaedt_app +from pyaedt.generic.filesystem import search_files +from pyaedt.generic.general_methods import generate_unique_name +from pyaedt.workflows.misc import get_aedt_version +from pyaedt.workflows.misc import get_arguments +from pyaedt.workflows.misc import get_port +from pyaedt.workflows.misc import get_process_id +from pyaedt.workflows.misc import is_student + +settings.use_grpc_api = True +settings.use_multi_desktop = True +non_graphical = True +extension_arguments = {"password": "", "application": "HFSS", "solution": "Modal", "file_path": ""} +extension_description = "Convert File from 22R2" +port = get_port() +version = get_aedt_version() +aedt_process_id = get_process_id() +is_student = is_student() + + +def frontend(): # pragma: no cover + + import tkinter + from tkinter import filedialog + from tkinter import ttk + + master = tkinter.Tk() + + master.geometry("750x250") + + master.title("Convert File from 22R2") + + # Configure style for ttk buttons + style = ttk.Style() + style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) + + var2 = tkinter.StringVar() + label2 = tkinter.Label(master, textvariable=var2) + var2.set("Browse file or folder:") + label2.grid(row=0, column=0, pady=10) + text = tkinter.Text(master, width=40, height=1) + text.grid(row=0, column=1, pady=10, padx=5) + + def edit_sols(self): + sol["values"] = tuple(solutions_types[appl.get()].keys()) + sol.current(0) + + var = tkinter.StringVar() + label = tkinter.Label(master, textvariable=var) + var.set("Password (Encrypted 3D Component Only):") + label.grid(row=1, column=0, pady=10) + pwd = tkinter.Entry(master, width=20, show="*") + pwd.insert(tkinter.END, "") + pwd.grid(row=1, column=1, pady=10, padx=5) + + var = tkinter.StringVar() + label = tkinter.Label(master, textvariable=var) + var.set("Application (3D Component Only):") + label.grid(row=2, column=0, pady=10) + appl = ttk.Combobox(master, width=40, validatecommand=edit_sols) # Set the width of the combobox + appl["values"] = ("HFSS", "Q3D Extractor", "Maxwell 3D", "Icepak") + appl.current(0) + appl.bind("<>", edit_sols) + appl.grid(row=2, column=1, pady=10, padx=5) + + var = tkinter.StringVar() + label = tkinter.Label(master, textvariable=var) + var.set("Solution (3D Component Only):") + label.grid(row=3, column=0, pady=10) + sol = ttk.Combobox(master, width=40) # Set the width of the combobox + sol["values"] = ttk.Combobox(master, width=40) # Set the width of the combobox + sol["values"] = tuple(solutions_types["HFSS"].keys()) + sol.current(0) + sol.grid(row=3, column=1, pady=10, padx=5) + + def browseFiles(): + filename = filedialog.askopenfilename( + initialdir="/", + title="Select a Electronics File", + filetypes=(("AEDT", ".aedt *.a3dcomp"), ("all files", "*.*")), + ) + text.insert(tkinter.END, filename) + + b1 = tkinter.Button(master, text="...", width=10, command=browseFiles) + b1.grid(row=0, column=2, pady=10) + + def callback(): + applications = {"HFSS": 0, "Icepak": 1, "Maxwell 3D": 2, "Q3D Extractor": 3} + master.password_ui = pwd.get() + master.application_ui = applications[appl.get()] + master.solution_ui = sol.get() + master.file_path_ui = text.get("1.0", tkinter.END).strip() + master.destroy() + + b3 = tkinter.Button(master, text="Ok", width=40, command=callback) + b3.grid(row=5, column=1, pady=10, padx=10) + + tkinter.mainloop() + + password_ui = getattr(master, "password_ui", extension_arguments["password"]) + application_ui = getattr(master, "application_ui", extension_arguments["application"]) + solution_ui = getattr(master, "solution_ui", extension_arguments["solution"]) + file_path_ui = getattr(master, "file_path_ui", extension_arguments["file_path"]) + + output_dict = { + "password": password_ui, + "application": application_ui, + "solution": solution_ui, + "file_path": file_path_ui, + } + return output_dict + + +def check_missing(input_object, output_object, file_path): + if output_object.design_type not in [ + "HFSS", + "Icepak", + "Q3d", + "2D Extractor", + "Maxwell 3D", + "Maxwell 2D", + "Mechanical", + ]: + return + object_list = input_object.modeler.object_names[::] + new_object_list = output_object.modeler.object_names[::] + un_classified_objects = output_object.modeler.unclassified_names[::] + unclassified = [i for i in object_list if i not in new_object_list and i in un_classified_objects] + disappeared = [i for i in object_list if i not in new_object_list and i not in un_classified_objects] + list_of_suppressed = [["Design", "Object", "Operation"]] + for obj_name in unclassified: + if obj_name in output_object.modeler.object_names: + continue + hist = output_object.modeler[obj_name].history() + for el_name, el in list(hist.children.items())[::-1]: + if "Suppress Command" in el.props: + el.props["Suppress Command"] = True + list_of_suppressed.append([output_object.design_name, obj_name, el_name]) + if obj_name in output_object.modeler.object_names: + break + for obj_name in disappeared: + input_object.export_3d_model( + file_name=obj_name, + file_format=".x_t", + file_path=input_object.working_directory, + assignment_to_export=[obj_name], + ) + output_object.modeler.import_3d_cad(os.path.join(input_object.working_directory, obj_name + ".x_t")) + list_of_suppressed.append([output_object.design_name, obj_name, "History"]) + from pyaedt.generic.general_methods import read_csv + from pyaedt.generic.general_methods import write_csv + + if file_path.split(".")[1] == "a3dcomp": + output_csv = os.path.join(file_path[:-8], "Import_Errors.csv")[::-1].replace("\\", "_", 1)[::-1] + else: + output_csv = os.path.join(file_path[:-5], "Import_Errors.csv")[::-1].replace("\\", "_", 1)[::-1] + if os.path.exists(output_csv): + data_read = read_csv(output_csv) + list_of_suppressed = data_read + list_of_suppressed[1:] + write_csv(output_csv, list_of_suppressed) + print(f"Errors saved in {output_csv}") + return output_csv, True + + +def convert_3d_component( + extension_args, + output_desktop, + input_desktop, +): + + file_path = extension_args["file_path"] + password = extension_args["password"] + solution = extension_args["solution"] + application = extension_args["application"] + + output_path = file_path[:-8] + f"_{version}.a3dcomp" + + if os.path.exists(output_path): + output_path = file_path[:-8] + generate_unique_name(f"_version", n=2) + ".a3dcomp" + app = Hfss + if application == 1: + app = Icepak + elif application == 2: + app = Maxwell3d + elif application == 3: + app = Q3d + app1 = app(aedt_process_id=input_desktop.aedt_process_id, solution_type=solution) + cmp = app1.modeler.insert_3d_component(file_path, password=password) + app_comp = cmp.edit_definition(password=password) + design_name = app_comp.design_name + app_comp.oproject.CopyDesign(design_name) + project_name2 = generate_unique_name("Proj_convert") + output_app = app(aedt_process_id=output_desktop.aedt_process_id, solution_type=solution, project=project_name2) + + output_app.oproject.Paste() + output_app = get_pyaedt_app(desktop=output_desktop, project_name=project_name2, design_name=design_name) + check_missing(app_comp, output_app, file_path) + output_app.modeler.create_3dcomponent( + output_path, + is_encrypted=True if password else False, + edit_password=password, + hide_contents=False, + allow_edit=True if password else False, + password_type="InternalPassword" if password else "UserSuppliedPassword", + ) + try: + output_desktop.DeleteProject(project_name2) + print(f"Project successfully deleted") + except Exception: + print(f"Error project was not closed") + print(f"3D Component {output_path} has been created.") + + +def convert_aedt( + extension_args, + output_desktop, + input_desktop, +): + + file_path = extension_args["file_path"] + + file_path = str(file_path) + a3d_component_path = str(file_path) + output_path = a3d_component_path[:-5] + f"_{version}.aedt" + if os.path.exists(output_path): + output_path = a3d_component_path[:-5] + generate_unique_name(f"_{version}", n=2) + ".aedt" + + input_desktop.load_project(file_path) + project_name = os.path.splitext(os.path.split(file_path)[-1])[0] + oproject2 = output_desktop.odesktop.NewProject(output_path) + project_name2 = os.path.splitext(os.path.split(output_path)[-1])[0] + + for design in input_desktop.design_list(): + + app1 = get_pyaedt_app(desktop=input_desktop, project_name=project_name, design_name=design) + app1.oproject.CopyDesign(app1.design_name) + oproject2.Paste() + output_app = get_pyaedt_app(desktop=output_desktop, project_name=project_name2, design_name=design) + check_missing(app1, output_app, file_path) + output_app.save_project() + input_desktop.odesktop.CloseProject(os.path.splitext(os.path.split(file_path)[-1])[0]) + + +def convert(args): + logger = logging.getLogger("Global") + if os.path.isdir(args["file_path"]): + files_path = search_files(args["file_path"], "*.a3dcomp") + files_path += search_files(args["file_path"], "*.aedt") + else: + files_path = [args["file_path"]] + output_desktop = Desktop( + new_desktop=True, + version=version, + port=port, + aedt_process_id=aedt_process_id, + student_version=is_student, + ) + input_desktop = Desktop(new_desktop=True, version=222, non_graphical=non_graphical) + for file in files_path: + try: + args["file_path"] = file + if args["file_path"].endswith("a3dcomp"): + convert_3d_component(args, output_desktop, input_desktop) + else: + convert_aedt(args, output_desktop, input_desktop) + except Exception: + logger.error(f"Failed to convert {file}") + input_desktop.release_desktop() + output_desktop.release_desktop(False, False) + + +if __name__ == "__main__": + args = get_arguments(extension_arguments, extension_description) + # Open UI + if not args["is_batch"]: # pragma: no cover + output = frontend() + if output: + for output_name, output_value in output.items(): + if output_name in extension_arguments: + args[output_name] = output_value + convert(args) diff --git a/pyaedt/workflows/templates/jupyter.py_build b/pyaedt/workflows/templates/jupyter.py_build index 02539325d4e..c08fb3cd945 100644 --- a/pyaedt/workflows/templates/jupyter.py_build +++ b/pyaedt/workflows/templates/jupyter.py_build @@ -80,6 +80,7 @@ def main(): pyaedt_utils.environment_variables(oDesktop) if is_linux: + pyaedt_utils.set_ansys_em_environment(oDesktop) if notebook_dir: command = [jupyter_exe, "lab", target,"--notebook-dir",notebook_dir] else: diff --git a/pyaedt/workflows/templates/pyaedt_console.py_build b/pyaedt/workflows/templates/pyaedt_console.py_build index 7c3053e57f8..e13661cd3f3 100644 --- a/pyaedt/workflows/templates/pyaedt_console.py_build +++ b/pyaedt/workflows/templates/pyaedt_console.py_build @@ -71,7 +71,7 @@ def main(): term = pyaedt_utils.get_linux_terminal() if not term: pyaedt_utils.show_error("No Terminals found.", oDesktop) - + pyaedt_utils.set_ansys_em_environment(oDesktop) command = [ term, "-e", diff --git a/pyaedt/workflows/templates/pyaedt_utils.py b/pyaedt/workflows/templates/pyaedt_utils.py index 108b8f7941f..a04927bf444 100644 --- a/pyaedt/workflows/templates/pyaedt_utils.py +++ b/pyaedt/workflows/templates/pyaedt_utils.py @@ -38,6 +38,12 @@ is_linux = os.name == "posix" +def set_ansys_em_environment(oDesktop): + variable = "ANSYSEM_ROOT{}".format(oDesktop.GetVersion()[2:6].replace(".", "")) + if variable not in os.environ: + os.environ[variable] = oDesktop.GetExeDir() + + def sanitize_interpreter_path(interpreter_path, version): python_version = "3_10" if version > "231" else "3_7" if version > "231" and python_version not in interpreter_path: diff --git a/pyaedt/workflows/templates/run_extension_manager.py_build b/pyaedt/workflows/templates/run_extension_manager.py_build index 8e92d3741c0..38811b98fcd 100644 --- a/pyaedt/workflows/templates/run_extension_manager.py_build +++ b/pyaedt/workflows/templates/run_extension_manager.py_build @@ -67,6 +67,7 @@ def main(): pyaedt_utils.environment_variables(oDesktop) # Open extension manager if is_linux: + pyaedt_utils.set_ansys_em_environment(oDesktop) command = [ python_exe, pyaedt_script, diff --git a/pyaedt/workflows/templates/run_pyaedt_script.py_build b/pyaedt/workflows/templates/run_pyaedt_script.py_build index f7425c0bbaf..86ccec2ac92 100644 --- a/pyaedt/workflows/templates/run_pyaedt_script.py_build +++ b/pyaedt/workflows/templates/run_pyaedt_script.py_build @@ -75,6 +75,7 @@ def main(): pyaedt_utils.environment_variables(oDesktop) # Run script if is_linux: + pyaedt_utils.set_ansys_em_environment(oDesktop) command = [ python_exe, pyaedt_script, diff --git a/pyaedt/workflows/templates/run_pyaedt_toolkit_script.py_build b/pyaedt/workflows/templates/run_pyaedt_toolkit_script.py_build index 355709a26cc..aa624beffdc 100644 --- a/pyaedt/workflows/templates/run_pyaedt_toolkit_script.py_build +++ b/pyaedt/workflows/templates/run_pyaedt_toolkit_script.py_build @@ -64,6 +64,7 @@ def main(): pyaedt_utils.environment_variables(oDesktop) # Run workflow if pyaedt_utils.is_linux: + pyaedt_utils.set_ansys_em_environment(oDesktop) my_env = dict(os.environ.copy()) command = [python_exe, pyaedt_script] subprocess.Popen(command, env=my_env) diff --git a/pyaedt/workflows/templates/run_pyedb_toolkit_script.py_build b/pyaedt/workflows/templates/run_pyedb_toolkit_script.py_build index 37ac2d0db09..a08cc97e06d 100644 --- a/pyaedt/workflows/templates/run_pyedb_toolkit_script.py_build +++ b/pyaedt/workflows/templates/run_pyedb_toolkit_script.py_build @@ -67,6 +67,7 @@ def main(): # Run workflow my_env = dict(os.environ.copy()) command = [python_exe, pyaedt_script] + pyaedt_utils.set_ansys_em_environment(oDesktop) subprocess.Popen(command, env=my_env) except Exception as e: pyaedt_utils.show_error(str(e), oDesktop) diff --git a/pyproject.toml b/pyproject.toml index c55cc77c652..de8be8d380d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,17 +37,17 @@ dependencies = [ "fpdf2", "jsonschema", "psutil", - "pyedb>=0.4.0", + "pyedb>=0.4.0; python_version == '3.7'", + "pyedb>=0.20.0; python_version > '3.7'", "pytomlpp", "rpyc>=6.0.0,<6.1", ] [project.optional-dependencies] tests = [ - "imageio>=2.30.0,<2.35", "ipython>=7.30.0,<8.27", "joblib>=1.0.0,<1.5", - "matplotlib>=3.5.0,<3.9", + "matplotlib>=3.5.0,<3.10", "mock>=5.1.0,<5.2", "numpy>=1.20.0,<2", "openpyxl>=3.1.0,<3.3", @@ -56,13 +56,12 @@ tests = [ "pytest>=7.4.0,<8.3", "pytest-cov>=4.0.0,<5.1", "pytest-xdist>=3.5.0,<3.7", - "pyvista>=0.38.0,<0.44", + "pyvista[io]>=0.38.0,<0.45", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", "scikit-rf>=0.30.0,<1.2", "SRTM.py", "utm", - "vtk==9.2.6; python_version < '3.12'", ] dotnet = [ "ansys-pythonnet>=3.1.0rc3", @@ -73,21 +72,18 @@ dotnet = [ ] doc = [ "ansys-sphinx-theme>=0.10.0,<0.17", - "imageio>=2.30.0,<2.35", - #"imageio-ffmpeg>=0.4.0,<0.5", "ipython>=7.34.0; python_version == '3.7'", "ipython>=8.13.0,<8.27; python_version > '3.7'", - #"ipywidgets>=8.0.0,<8.2", "joblib>=1.3.0,<1.5", "jupyterlab>=4.0.0,<4.3", - "matplotlib>=3.5.0,<3.9", + "matplotlib>=3.5.0,<3.10", "nbsphinx>=0.9.0,<0.10", "numpydoc>=1.5.0,<1.8", "openpyxl>=3.0.0,<3.2", "osmnx>=1.1.0,<1.10", "pypandoc>=1.10.0,<1.14", #"pytest-sphinx", - "pyvista>=0.38.0,<0.44", + "pyvista[io]>=0.38.0,<0.45", "recommonmark", "scikit-rf>=0.30.0,<1.2", "Sphinx==5.3.0; python_version == '3.7'", @@ -103,12 +99,9 @@ doc = [ #"sphinxcontrib-websupport", "SRTM.py", "utm", - "vtk==9.2.6; python_version < '3.12'", ] doc-no-examples = [ "ansys-sphinx-theme>=0.10.0,<0.17", - "imageio>=2.30.0,<2.35", - #"imageio-ffmpeg", "numpydoc>=1.5.0,<1.8", "recommonmark", "Sphinx==5.3.0; python_version == '3.7'", @@ -122,39 +115,35 @@ doc-no-examples = [ #"sphinx-notfound-page", #"sphinxcontrib-websupport", "sphinx_design>=0.4.0,<0.7", - "matplotlib>=3.5.0,<3.9", + "matplotlib>=3.5.0,<3.10", "scikit-rf>=0.30.0,<1.2", ] all = [ - "imageio>=2.30.0,<2.35", - "matplotlib>=3.5.0,<3.9", + "matplotlib>=3.5.0,<3.10", "numpy>=1.20.0,<2", "openpyxl>=3.1.0,<3.3", "osmnx>=1.1.0,<1.10", "pandas>=1.1.0,<2.3", - "pyvista>=0.38.0,<0.44", + "pyvista[io]>=0.38.0,<0.45", "fast-simplification>=0.1.7", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", "scikit-rf>=0.30.0,<1.2", "SRTM.py", "utm", - "vtk==9.2.6; python_version < '3.12'", ] installer = [ - "imageio>=2.30.0,<2.35", - "matplotlib>=3.5.0,<3.9", + "matplotlib>=3.5.0,<3.10", "numpy>=1.20.0,<2", "openpyxl>=3.1.0,<3.3", "osmnx>=1.1.0,<1.10", "pandas>=1.1.0,<2.3", - "pyvista>=0.38.0,<0.44", + "pyvista[io]>=0.38.0,<0.45", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", "scikit-rf>=0.30.0,<1.2", "SRTM.py", "utm", - "vtk==9.2.6; python_version < '3.12'", "jupyterlab>=3.6.0,<4.3", "ipython>=7.30.0,<8.27", "ipyvtklink>=0.2.0,<0.2.4",