diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f490960fd1..a15b8612131 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,7 +48,7 @@ repos: # validate GitHub workflow files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.5 + rev: 0.28.6 hooks: - id: check-github-workflows diff --git a/_unittest/example_models/T98/cylinder_mesh.msh b/_unittest/example_models/T98/cylinder_mesh.msh new file mode 100644 index 00000000000..3481636c14e Binary files /dev/null and b/_unittest/example_models/T98/cylinder_mesh.msh differ diff --git a/_unittest/example_models/TMaxwell/maxwell_e_line_export_field.aedtz b/_unittest/example_models/TMaxwell/maxwell_e_line_export_field.aedtz new file mode 100644 index 00000000000..dee47c46580 Binary files /dev/null and b/_unittest/example_models/TMaxwell/maxwell_e_line_export_field.aedtz differ diff --git a/_unittest/test_27_Maxwell2D.py b/_unittest/test_27_Maxwell2D.py index d19bd64ce6b..1c25f7594ac 100644 --- a/_unittest/test_27_Maxwell2D.py +++ b/_unittest/test_27_Maxwell2D.py @@ -45,6 +45,8 @@ ctrl_prg = "TimeStepCtrl" ctrl_prg_file = "timestep_only.py" +m2d_fields = "maxwell_e_line_export_field" + @pytest.fixture(scope="class") def aedtapp(add_app): @@ -63,11 +65,18 @@ def m2d_ctrl_prg(add_app): return app +@pytest.fixture(scope="class") +def m2d_field_export(add_app): + app = add_app(application=Maxwell2d, project_name=m2d_fields, subfolder=test_subfolder) + return app + + class TestClass: @pytest.fixture(autouse=True) - def init(self, aedtapp, m2d_ctrl_prg, local_scratch): + def init(self, aedtapp, m2d_ctrl_prg, m2d_field_export, local_scratch): self.aedtapp = aedtapp self.m2d_ctrl_prg = m2d_ctrl_prg + self.m2d_field_export = m2d_field_export self.local_scratch = local_scratch def test_03_assign_initial_mesh_from_slider(self): @@ -583,3 +592,10 @@ def test_37_boundaries_by_type(self): wdg_group = self.aedtapp.boundaries_by_type["Winding Group"] assert wdg_group assert len(wdg_group) == len([bound for bound in self.aedtapp.boundaries if bound.type == "Winding Group"]) + + 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" + ) + assert os.path.exists(output_file) diff --git a/_unittest/test_98_Icepak.py b/_unittest/test_98_Icepak.py index 3e292a9a0d9..c58c0c6c626 100644 --- a/_unittest/test_98_Icepak.py +++ b/_unittest/test_98_Icepak.py @@ -347,8 +347,11 @@ def test_12a_AssignMeshOperation(self): mesh_level_Filter = "2" component_name = ["RadioBoard1_1"] mesh_level_RadioPCB = "1" - test = self.aedtapp.mesh.assign_mesh_level_to_group(mesh_level_Filter, group_name) + assert self.aedtapp.mesh.assign_mesh_level_to_group(mesh_level_Filter, group_name) + test = self.aedtapp.mesh.assign_mesh_level_to_group(mesh_level_Filter, group_name, name="Test") assert test + test2 = self.aedtapp.mesh.assign_mesh_level_to_group(mesh_level_Filter, group_name, name="Test") + assert test.name != test2.name # assert self.aedtapp.mesh.assignMeshLevel2Component(mesh_level_RadioPCB, component_name) test = self.aedtapp.mesh.assign_mesh_region(component_name, mesh_level_RadioPCB, is_submodel=True) assert test @@ -1741,3 +1744,55 @@ def test_78_restart_solution(self): "test_78-1", "{} : SteadyState".format(s1.name), project="FakeFolder123" ) assert not s2.start_continue_from_previous_setup("test_78-12", "{} : SteadyState".format(s1.name)) + + def test_79_mesh_reuse(self): + self.aedtapp.insert_design("test_79") + self.aedtapp.set_active_design("test_79") + cylinder = self.aedtapp.modeler.create_cylinder(1, [0, 0, 0], 5, 30) + assert not self.aedtapp.mesh.assign_mesh_reuse( + cylinder.name, + os.path.join(local_path, "../_unittest/example_models", test_subfolder, "nonexistent_cylinder_mesh.msh"), + ) + assert self.aedtapp.mesh.assign_mesh_reuse( + cylinder.name, os.path.join(local_path, "../_unittest/example_models", test_subfolder, "cylinder_mesh.msh") + ) + assert self.aedtapp.mesh.assign_mesh_reuse( + cylinder.name, + os.path.join(local_path, "../_unittest/example_models", test_subfolder, "cylinder_mesh.msh"), + "name_reuse", + ) + assert self.aedtapp.mesh.assign_mesh_reuse( + cylinder.name, + os.path.join(local_path, "../_unittest/example_models", test_subfolder, "cylinder_mesh.msh"), + "name_reuse", + ) + + def test_80_global_mesh_region(self): + self.aedtapp.insert_design("test_80") + self.aedtapp.set_active_design("test_80") + g_m_r = self.aedtapp.mesh.global_mesh_region + assert g_m_r + assert g_m_r.global_region.object.name == "Region" + assert g_m_r.global_region.padding_values == ["50", "50", "50", "50", "50", "50"] + assert g_m_r.global_region.padding_types == [ + "Percentage Offset", + "Percentage Offset", + "Percentage Offset", + "Percentage Offset", + "Percentage Offset", + "Percentage Offset", + ] + g_m_r.global_region.positive_z_padding_type = "Absolute Offset" + g_m_r.global_region.positive_z_padding = "5 mm" + assert g_m_r.global_region.padding_types[-2] == "Absolute Offset" + assert g_m_r.global_region.padding_values[-2] == "5mm" + g_m_r.settings["MeshRegionResolution"] = 3 + g_m_r.update() + assert g_m_r.settings["MeshRegionResolution"] == 3 + g_m_r.manual_settings = True + with pytest.raises(KeyError): + g_m_r.settings["MeshRegionResolution"] + g_m_r.settings["MaxElementSizeX"] = "500um" + g_m_r.update() + g_m_r.global_region.object.material_name = "Carbon Monoxide" + assert g_m_r.global_region.object.material_name == "Carbon Monoxide" diff --git a/doc/source/API/Mesh.rst b/doc/source/API/Mesh.rst index 7b1a362ac43..f7bbadfb5c7 100644 --- a/doc/source/API/Mesh.rst +++ b/doc/source/API/Mesh.rst @@ -26,6 +26,23 @@ They are accessible through the mesh property: close_on_exit=True, student_version=False) # This call returns the Mesh class my_mesh = app.mesh - # This call executes a Mesh method and creates an object to control the mesh operation + # This call executes a ``Mesh`` method and creates an object to control the mesh operation mesh_operation_object = my_mesh.assign_surface_mesh("MyBox", 2) ... + +Icepak mesh +~~~~~~~~~~~~~~~ + +These objects are relevant objects while using the ``MeshIcepak`` class: + +.. currentmodule:: pyaedt.modules.MeshIcepak + +.. autosummary:: + :toctree: _autosummary + :nosignatures: + + + Region + SubRegion + MeshRegion + GlobalMeshRegion \ No newline at end of file diff --git a/doc/source/Getting_started/Installation.rst b/doc/source/Getting_started/Installation.rst index 586eb658f75..16e491ee992 100644 --- a/doc/source/Getting_started/Installation.rst +++ b/doc/source/Getting_started/Installation.rst @@ -65,6 +65,9 @@ The user can select the AEDT application to install the specific workflow. :width: 400 :alt: PyAEDT toolkit manager 2 +For additional information about AEDT extensions, +see `Extensions `_. + Install on CPython from PyPI ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/Resources/icepak_global_mesh_region_objects.png b/doc/source/Resources/icepak_global_mesh_region_objects.png new file mode 100644 index 00000000000..ac8aecb20bc Binary files /dev/null and b/doc/source/Resources/icepak_global_mesh_region_objects.png differ diff --git a/doc/source/Resources/toolkits_ribbon.png b/doc/source/Resources/toolkits_ribbon.png index 8f33f7825f2..5758f592cbb 100644 Binary files a/doc/source/Resources/toolkits_ribbon.png and b/doc/source/Resources/toolkits_ribbon.png differ diff --git a/doc/source/User_guide/extensions.rst b/doc/source/User_guide/extensions.rst new file mode 100644 index 00000000000..888e51f43c0 --- /dev/null +++ b/doc/source/User_guide/extensions.rst @@ -0,0 +1,36 @@ +Extensions +========== + +Extensions provide a simplified interface to perform automated workflows in AEDT. +In AEDT, you can use the Extension Manager to add or remove extensions. +For more information, see `Extension Manager `_. + +Extensions are generally tool-specific and are therefore only accessible given the appropriate context. The following sections provide further clarification. + +You can launch extensions in standalone mode from the console or a Python script. + +Project extensions +================== + +Project extension apply to all extensions that are applicable for all AEDT applications. + +.. grid:: 2 + + .. grid-item-card:: Import Nastran + :link: pyaedt_extensions_doc/project + :link-type: doc + + Import a Nastran or STL file in any 3D modeler application. + +.. toctree:: + :hidden: + :maxdepth: 2 + + pyaedt_extensions_doc/project + +HFSS extensions +=============== + + +HFSS 3D Layout extensions +========================= diff --git a/doc/source/User_guide/index.rst b/doc/source/User_guide/index.rst index cd8782d6a74..f687c34ea27 100644 --- a/doc/source/User_guide/index.rst +++ b/doc/source/User_guide/index.rst @@ -18,6 +18,13 @@ For end-to-end examples, see `Examples >> oModule.AssignMeshOperation + """ + if not os.path.exists(mesh_file): + self._app.logger.error("Mesh file does not exist.") + return False + if name: + for el in self.meshoperations: + if el.name == name: + name = generate_unique_name(name) + break + else: + name = generate_unique_name("MeshReuse") + if not isinstance(assignment, list): + assignment = [assignment] + props = OrderedDict( + {"Enable": True, "Mesh Reuse Enabled": True, "Mesh Reuse File": mesh_file, "Objects": assignment} + ) + mop = MeshOperation(self, name, props, "Icepak") + mop.create() + self.meshoperations.append(mop) + return mop diff --git a/pyaedt/modules/PostProcessor.py b/pyaedt/modules/PostProcessor.py index 3d9488924dd..9ec0c4a5c0e 100644 --- a/pyaedt/modules/PostProcessor.py +++ b/pyaedt/modules/PostProcessor.py @@ -3024,11 +3024,17 @@ def export_field_file( self.ofieldsreporter.EnterVol(assignment) elif objects_type == "Surf": self.ofieldsreporter.EnterSurf(assignment) + elif objects_type == "Line": + self.ofieldsreporter.EnterLine(assignment) else: self.logger.error("No correct choice.") return False self.ofieldsreporter.CalcOp("Value") - self.ofieldsreporter.CalculatorWrite(output_dir, ["Solution:=", solution], variation) + if objects_type == "Line": + args = ["Solution:=", solution, "Geometry:=", assignment, "GeometryType:=", objects_type] + else: + args = ["Solution:=", solution] + self.ofieldsreporter.CalculatorWrite(output_dir, args, variation) elif sample_points_file: export_options = [ "NAME:ExportOption", diff --git a/pyproject.toml b/pyproject.toml index 606bb19e2c0..9b165f845ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ tests = [ "pyvista>=0.38.0,<0.44", # 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.1", + "scikit-rf>=0.30.0,<1.2", "SRTM.py", "utm", "vtk==9.2.6; python_version < '3.12'", @@ -89,7 +89,7 @@ doc = [ #"pytest-sphinx", "pyvista>=0.38.0,<0.44", "recommonmark", - "scikit-rf>=0.30.0,<1.1", + "scikit-rf>=0.30.0,<1.2", "Sphinx==5.3.0; python_version == '3.7'", "Sphinx>=7.1.0,<7.4; python_version > '3.7'", "sphinx-autobuild==2021.3.14; python_version == '3.7'", @@ -134,7 +134,7 @@ all = [ "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.1", + "scikit-rf>=0.30.0,<1.2", "SRTM.py", "utm", "vtk==9.2.6; python_version < '3.12'", @@ -149,7 +149,7 @@ installer = [ "pyvista>=0.38.0,<0.44", # 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.1", + "scikit-rf>=0.30.0,<1.2", "SRTM.py", "utm", "vtk==9.2.6; python_version < '3.12'",