diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 66133ced6e0..d36d7ebaad3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ exclude: | repos: - repo: https://github.com/psf/black - rev: 24.4.0 # IF VERSION CHANGES --> MODIFY "blacken-docs" MANUALLY AS WELL!! + rev: 24.4.2 # IF VERSION CHANGES --> MODIFY "blacken-docs" MANUALLY AS WELL!! hooks: - id: black args: @@ -56,7 +56,7 @@ repos: rev: 1.16.0 hooks: - id: blacken-docs - additional_dependencies: [black==24.4.0] + additional_dependencies: [black==24.4.2] # This validates our pre-commit.ci configuration - repo: https://github.com/pre-commit-ci/pre-commit-ci-config diff --git a/_unittest/test_01_Design.py b/_unittest/test_01_Design.py index 3cba663e92b..ae2150c5391 100644 --- a/_unittest/test_01_Design.py +++ b/_unittest/test_01_Design.py @@ -10,10 +10,12 @@ from pyaedt import Hfss3dLayout from pyaedt import Icepak from pyaedt import get_pyaedt_app +from pyaedt.application.Design import DesignSettings from pyaedt.application.aedt_objects import AedtObjects from pyaedt.application.design_solutions import model_names from pyaedt.generic.general_methods import is_linux from pyaedt.generic.general_methods import settings +from pyaedt.workflows import customize_automation_tab test_subfolder = "T01" if config["desktopVersion"] > "2022.2": @@ -397,17 +399,18 @@ def test_36_test_load(self, add_app): assert True def test_37_add_custom_toolkit(self, desktop): - assert desktop.get_available_toolkits() + assert customize_automation_tab.available_toolkits def test_38_toolkit(self, desktop): file = os.path.join(self.local_scratch.path, "test.py") with open(file, "w") as f: f.write("import pyaedt\n") - assert desktop.add_script_to_menu( - "test_toolkit", - file, + assert customize_automation_tab.add_script_to_menu( + desktop_object=self.aedtapp.desktop_class, name="test_toolkit", script_file=file + ) + assert customize_automation_tab.remove_script_from_menu( + desktop_object=self.aedtapp.desktop_class, name="test_toolkit" ) - assert desktop.remove_script_from_menu("test_toolkit") def test_39_load_project(self, desktop): new_project = os.path.join(self.local_scratch.path, "new.aedt") @@ -418,9 +421,9 @@ def test_39_load_project(self, desktop): def test_40_get_design_settings(self, add_app): ipk = add_app(application=Icepak) - design_settings_dict = ipk.design_settings() + design_settings_dict = ipk.design_settings - assert isinstance(design_settings_dict, dict) + assert isinstance(design_settings_dict, DesignSettings) assert "AmbTemp" in design_settings_dict assert "AmbRadTemp" in design_settings_dict assert "GravityVec" in design_settings_dict diff --git a/_unittest/test_01_toolkit_icons.py b/_unittest/test_01_toolkit_icons.py index 1cad078d49e..55ec3a74cbd 100644 --- a/_unittest/test_01_toolkit_icons.py +++ b/_unittest/test_01_toolkit_icons.py @@ -1,9 +1,14 @@ import os -import xml.etree.ElementTree as ET + +import defusedxml.ElementTree as ET +import defusedxml.minidom + +defusedxml.defuse_stdlib() + import pytest -from pyaedt.misc.aedtlib_personalib_install import write_tab_config +from pyaedt.workflows.customize_automation_tab import add_automation_tab @pytest.fixture(scope="module", autouse=True) @@ -17,8 +22,7 @@ def init(self, local_scratch): self.local_scratch = local_scratch def test_00_write_new_xml(self): - file_path = os.path.join(self.local_scratch.path, "TabConfig.xml") - write_tab_config(os.path.dirname(file_path), self.local_scratch.path) + file_path = add_automation_tab(name="Test", lib_dir=self.local_scratch.path) root = self.validate_file_exists_and_pyaedt_tabs_added(file_path) panels = root.findall("./panel") panel_names = [panel.attrib["label"] for panel in panels] @@ -29,7 +33,7 @@ def test_01_add_pyaedt_config_to_existing_existing_xml(self): First write a dummy XML with a different Panel and then add PyAEDT's tabs :return: """ - file_path = os.path.join(self.local_scratch.path, "TabConfig.xml") + file_path = os.path.join(self.local_scratch.path, "Project", "TabConfig.xml") with open(file_path, "w") as fid: fid.write( """ @@ -47,7 +51,7 @@ def test_01_add_pyaedt_config_to_existing_existing_xml(self): """ ) - write_tab_config(os.path.dirname(file_path), self.local_scratch.path) + file_path = add_automation_tab(name="Test", lib_dir=self.local_scratch.path) root = self.validate_file_exists_and_pyaedt_tabs_added(file_path) panels = root.findall("./panel") panel_names = [panel.attrib["label"] for panel in panels] @@ -55,7 +59,7 @@ def test_01_add_pyaedt_config_to_existing_existing_xml(self): assert "Panel_1" in panel_names def test_03_overwrite_existing_pyaedt_config(self): - file_path = os.path.join(self.local_scratch.path, "TabConfig.xml") + file_path = os.path.join(self.local_scratch.path, "Project", "TabConfig.xml") with open(file_path, "w") as fid: fid.write( """ @@ -72,14 +76,14 @@ def test_03_overwrite_existing_pyaedt_config(self): """ ) - write_tab_config(os.path.dirname(file_path), self.local_scratch.path) + file_path = add_automation_tab(name="Test", lib_dir=self.local_scratch.path) root = self.validate_file_exists_and_pyaedt_tabs_added(file_path) panels = root.findall("./panel") panel_names = [panel.attrib["label"] for panel in panels] - assert len(panel_names) == 1 + assert len(panel_names) == 2 def test_04_write_to_existing_file_but_no_panels(self): - file_path = os.path.join(self.local_scratch.path, "TabConfig.xml") + file_path = os.path.join(self.local_scratch.path, "Project", "TabConfig.xml") with open(file_path, "w") as fid: fid.write( """ @@ -88,7 +92,7 @@ def test_04_write_to_existing_file_but_no_panels(self): """ ) - write_tab_config(os.path.dirname(file_path), self.local_scratch.path) + file_path = add_automation_tab(name="Test", lib_dir=self.local_scratch.path) root = self.validate_file_exists_and_pyaedt_tabs_added(file_path) junks = root.findall("./junk") junk_names = [junk.attrib["label"] for junk in junks] @@ -98,15 +102,13 @@ def test_04_write_to_existing_file_but_no_panels(self): panel_names = [panel.attrib["label"] for panel in panels] assert len(panel_names) == 1 - def validate_file_exists_and_pyaedt_tabs_added(self, file_path): + @staticmethod + def validate_file_exists_and_pyaedt_tabs_added(file_path): assert os.path.isfile(file_path) is True assert ET.parse(file_path) is not None tree = ET.parse(file_path) root = tree.getroot() panels = root.findall("./panel") panel_names = [panel.attrib["label"] for panel in panels] - assert "Panel_PyAEDT" in panel_names - files_to_verify = ["images/large/pyansys.png", "images/gallery/PyAEDT.png"] - for file_name in files_to_verify: - assert os.path.isfile(os.path.join(os.path.dirname(file_path), file_name)) + assert "Panel_PyAEDT_Toolkits" in panel_names return root diff --git a/_unittest/test_08_Primitives3D.py b/_unittest/test_08_Primitives3D.py index 753c8ee8684..605d28a632b 100644 --- a/_unittest/test_08_Primitives3D.py +++ b/_unittest/test_08_Primitives3D.py @@ -1764,7 +1764,7 @@ def test_83_cover_face(self): o1 = self.aedtapp.modeler.create_circle(cs_plane=0, position=[0, 0, 0], radius=10) assert self.aedtapp.modeler.cover_faces(o1) - def test_84_replace_3dcomponent(self): + def test_84_replace_3d_component(self): self.aedtapp["test_variable"] = "20mm" box1 = self.aedtapp.modeler.create_box([0, 0, 0], [10, "test_variable", 30]) box2 = self.aedtapp.modeler.create_box([0, 0, 0], ["test_variable", 100, 30]) @@ -1782,7 +1782,8 @@ def test_84_replace_3dcomponent(self): assert len(self.aedtapp.modeler.user_defined_components) == 2 @pytest.mark.skipif(config["desktopVersion"] < "2023.1", reason="Method available in beta from 2023.1") - def test_85_insert_layoutcomponent(self): + @pytest.mark.skipif(is_linux, reason="EDB object is not loaded") + def test_85_insert_layout_component(self): self.aedtapp.insert_design("LayoutComponent") self.aedtapp.solution_type = "Modal" assert not self.aedtapp.modeler.insert_layout_component( diff --git a/_unittest/test_09_Primitives2D.py b/_unittest/test_09_Primitives2D.py index 4cd695c39d1..1f5965accdd 100644 --- a/_unittest/test_09_Primitives2D.py +++ b/_unittest/test_09_Primitives2D.py @@ -74,10 +74,14 @@ def test_06_create_region(self): if self.aedtapp.modeler["Region"]: self.aedtapp.modeler.delete("Region") assert "Region" not in self.aedtapp.modeler.object_names - assert self.aedtapp.modeler.create_region([20, "50", "100mm", 20], False) + assert self.aedtapp.modeler.create_region([20, "50", "100mm", 20], "Absolute Offset") self.aedtapp.modeler["Region"].delete() - region = self.aedtapp.modeler.create_region("100", True) + region = self.aedtapp.modeler.create_region("100", "Percentage Offset") region.delete() + # test backward compatibility + region = self.aedtapp.modeler.create_region(pad_percent=[100, 10, 5, 2], pad_type=True) + region.delete() + # region = self.aedtapp.modeler.create_region([100, 100, 100, 100]) assert region.solve_inside assert region.model diff --git a/_unittest/test_09_VariableManager.py b/_unittest/test_09_VariableManager.py index df4adad7f8d..7f934255478 100644 --- a/_unittest/test_09_VariableManager.py +++ b/_unittest/test_09_VariableManager.py @@ -252,10 +252,10 @@ def test_07_addition(self): v2 = Variable(3) v3 = Variable("3mA") v4 = Variable("10A") - with pytest.raises(AssertionError): + with pytest.raises(ValueError): v1 + v2 - with pytest.raises(AssertionError): + with pytest.raises(ValueError): v2 + v1 result_1 = v2 + v2 result_2 = v3 + v4 @@ -278,10 +278,10 @@ def test_08_subtraction(self): v3 = Variable("3mA") v4 = Variable("10A") - with pytest.raises(AssertionError): + with pytest.raises(ValueError): v1 - v2 - with pytest.raises(AssertionError): + with pytest.raises(ValueError): v2 - v1 result_1 = v2 - v2 diff --git a/_unittest/test_21_Circuit.py b/_unittest/test_21_Circuit.py index fb1b923d90d..2fdce69845e 100644 --- a/_unittest/test_21_Circuit.py +++ b/_unittest/test_21_Circuit.py @@ -50,7 +50,6 @@ def examples(local_scratch): return netlist_file1, netlist_file2, touchstone_file, touchstone_file2 -@pytest.mark.skipif(is_linux, reason="Multiple tests are not passing in Linux with AEDT 2024R1") class TestClass: @pytest.fixture(autouse=True) def init(self, aedtapp, circuitprj, local_scratch, examples): @@ -207,10 +206,9 @@ def test_17_create_setup(self): ] assert LNA_setup.update() - @pytest.mark.skipif(is_linux, reason="To be investigated on linux.") def test_18_export_touchstone(self): assert self.aedtapp.analyze("Dom_LNA") - time.sleep(30) + time.sleep(2) solution_name = "Dom_LNA" sweep_name = None file_name = os.path.join(self.local_scratch.path, "new.s2p") @@ -220,7 +218,7 @@ def test_18_export_touchstone(self): assert self.aedtapp.setup_names[0] == solution_name assert self.aedtapp.export_touchstone(solution_name, sweep_name) - def test_19A_create_sweeps(self): + def test_19a_create_sweeps(self): setup_name = "Sweep_LNA" LNA_setup = self.aedtapp.create_setup(setup_name) LNA_setup.add_sweep_step("Freq", 1, 2, 0.01, "GHz", override_existing_sweep=True) @@ -233,7 +231,7 @@ def test_19A_create_sweeps(self): assert LNA_setup.props["SweepDefinition"][1]["Variable"] == "Temp" assert LNA_setup.props["SweepDefinition"][1]["Data"] == "DEC 20cel 100cel 81" - def test_19B_create_EyE_setups(self): + def test_19b_create_eye_setups(self): setup_name = "Dom_Verify" assert self.aedtapp.create_setup(setup_name, "NexximVerifEye") setup_name = "Dom_Quick" @@ -241,8 +239,13 @@ def test_19B_create_EyE_setups(self): setup_name = "Dom_AMI" assert self.aedtapp.create_setup(setup_name, "NexximAMI") - def test_20_create_AMI_plots(self, add_app): + @pytest.mark.skipif( + is_linux and config["desktopVersion"] == "2024.1", + reason="Project with multiple circuit designs is not working.", + ) + def test_20a_create_ami_plots(self, add_app): ami_design = add_app(ami_project, design_name="Models Init Only", application=Circuit, subfolder=test_subfolder) + report_name = "MyReport" assert ( ami_design.post.create_ami_initial_response_plot( @@ -277,7 +280,7 @@ def test_20_create_AMI_plots(self, add_app): ) @pytest.mark.skipif(config["desktopVersion"] > "2021.2", reason="Skipped on versions higher than 2021.2") - def test_20B_create_AMI_plots(self): + def test_20b_create_ami_plots(self): assert ( self.aedtapp.post.create_statistical_eye_plot( "Dom_Verify", @@ -415,12 +418,20 @@ def test_31_duplicate(self): # pragma: no cover def test_32_push_down(self): self.aedtapp.insert_design("Circuit_Design_Push_Down") subcircuit_1 = self.aedtapp.modeler.schematic.create_subcircuit(location=[0.0, 0.0]) - active_project_name_1 = self.aedtapp.oproject.GetActiveDesign().GetName() + active_project = self.aedtapp.oproject.GetActiveDesign() + if is_linux and config["desktopVersion"] == "2024.1": + time.sleep(1) + self.aedtapp._desktop.CloseAllWindows() + active_project_name_1 = active_project.GetName() self.aedtapp.pop_up() subcircuit_2 = self.aedtapp.modeler.schematic.create_subcircuit( location=[0.0, 0.0], nested_subcircuit_id=subcircuit_1.component_info["RefDes"] ) - active_project_name_3 = self.aedtapp.oproject.GetActiveDesign().GetName() + active_project = self.aedtapp.oproject.GetActiveDesign() + if is_linux and config["desktopVersion"] == "2024.1": + time.sleep(1) + self.aedtapp._desktop.CloseAllWindows() + active_project_name_3 = active_project.GetName() assert active_project_name_1 == active_project_name_3 assert subcircuit_2.component_info["RefDes"] == "U2" assert self.aedtapp.push_down(subcircuit_1) @@ -428,10 +439,18 @@ def test_32_push_down(self): def test_33_pop_up(self): self.aedtapp.insert_design("Circuit_Design_Pop_Up") assert self.aedtapp.pop_up() - active_project_name_1 = self.aedtapp.oproject.GetActiveDesign().GetName() + active_project = self.aedtapp.oproject.GetActiveDesign() + if is_linux and config["desktopVersion"] == "2024.1": + time.sleep(1) + self.aedtapp._desktop.CloseAllWindows() + active_project_name_1 = active_project.GetName() self.aedtapp.modeler.schematic.create_subcircuit(location=[0.0, 0.0]) assert self.aedtapp.pop_up() - active_project_name_2 = self.aedtapp.oproject.GetActiveDesign().GetName() + active_project = self.aedtapp.oproject.GetActiveDesign() + if is_linux and config["desktopVersion"] == "2024.1": + time.sleep(1) + self.aedtapp._desktop.CloseAllWindows() + active_project_name_2 = active_project.GetName() assert active_project_name_1 == active_project_name_2 def test_34_activate_variables(self): @@ -481,14 +500,15 @@ def test_38_browse_log_file(self): self.aedtapp.modeler.components.create_interface_port("net_10", (0.01, 0)) lna = self.aedtapp.create_setup("mylna", self.aedtapp.SETUPS.NexximLNA) lna.props["SweepDefinition"]["Data"] = "LINC 0Hz 1GHz 101" - assert not self.aedtapp.browse_log_file() self.aedtapp.analyze() + time.sleep(2) assert self.aedtapp.browse_log_file() - self.aedtapp.save_project() - assert self.aedtapp.browse_log_file() - assert not self.aedtapp.browse_log_file(os.path.join(self.aedtapp.working_directory, "logfiles")) - assert self.aedtapp.browse_log_file(self.aedtapp.working_directory) + if not is_linux: + self.aedtapp.save_project() + assert self.aedtapp.browse_log_file() + assert not self.aedtapp.browse_log_file(os.path.join(self.aedtapp.working_directory, "logfiles")) + assert self.aedtapp.browse_log_file(self.aedtapp.working_directory) def test_39_export_results_circuit(self): exported_files = self.aedtapp.export_results() @@ -691,9 +711,10 @@ def test_41_assign_excitations(self, add_app): assert "PortTest" in c.excitations c.excitation_objects["PortTest"].delete() assert len(c.excitation_objects) == 0 - self.aedtapp.save_project() - c = add_app(application=Circuit, design_name="sources") - assert c.sources + if not is_linux: + self.aedtapp.save_project() + c = add_app(application=Circuit, design_name="sources") + assert c.sources def test_41_set_variable(self): self.aedtapp.variable_manager.set_variable("var_test", expression="123") @@ -772,6 +793,7 @@ def test_43_create_and_change_prop_text(self): assert self.aedtapp.modeler.create_text("text test", "1000mil", "-2000mil") @pytest.mark.skipif(config["NonGraphical"], reason="Change property doesn't work in non-graphical mode.") + @pytest.mark.skipif(is_linux and config["desktopVersion"] == "2024.1", reason="Schematic has to be closed.") def test_44_change_text_property(self): self.aedtapp.set_active_design("text") text_id = self.aedtapp.oeditor.GetAllGraphics()[0].split("@")[1] @@ -785,6 +807,7 @@ def test_44_change_text_property(self): assert not self.aedtapp.modeler.change_text_property(text_id, "Invalid", {}) @pytest.mark.skipif(config["NonGraphical"], reason="Change property doesn't work in non-graphical mode.") + @pytest.mark.skipif(is_linux and config["desktopVersion"] == "2024.1", reason="Schematic has to be closed.") def test_45_create_circuit_from_multizone_layout(self, add_edb): edb = add_edb(project_name="multi_zone_project") common_reference_net = "gnd" @@ -799,7 +822,6 @@ def test_45_create_circuit_from_multizone_layout(self, add_edb): assert self.aedtapp.remove_all_unused_definitions() def test_46_create_vpwl(self): - # default inputs myres = self.aedtapp.modeler.schematic.create_voltage_pwl(name="V1") assert myres.refdes != "" @@ -834,6 +856,9 @@ def test_47_automatic_lna(self): ) assert status + @pytest.mark.skipif( + config["NonGraphical"] and is_linux, reason="Method is not working in Linux and non-graphical mode." + ) def test_48_automatic_tdr(self): touchstone_file = os.path.join(local_path, "example_models", test_subfolder, touchstone_custom) @@ -850,6 +875,7 @@ def test_48_automatic_tdr(self): ) assert result + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical.") def test_49_automatic_ami(self): touchstone_file = os.path.join(local_path, "example_models", test_subfolder, touchstone_custom) ami_file = os.path.join(local_path, "example_models", test_subfolder, "pcieg5_32gt.ibs") diff --git a/_unittest/test_22_Circuit_DynamicLink.py b/_unittest/test_22_Circuit_DynamicLink.py index 006542b7cb4..f1864fa6ee0 100644 --- a/_unittest/test_22_Circuit_DynamicLink.py +++ b/_unittest/test_22_Circuit_DynamicLink.py @@ -8,7 +8,7 @@ from pyaedt import Q2d from pyaedt import Q3d from pyaedt import is_ironpython -from pyaedt.generic.general_methods import is_linux +from pyaedt import is_linux test_subfloder = "T22" test_project_name = "Dynamic_Link" @@ -81,10 +81,10 @@ def test_02_add_subcircuits_3dlayout(self): assert hfss3Dlayout_comp.id == 86 assert hfss3Dlayout_comp - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical.") def test_03_add_subcircuits_hfss_link(self, add_app): pin_names = self.aedtapp.get_source_pin_names(src_design_name, src_project_name, self.src_project_file, 2) - assert len(pin_names) == 4 assert "usb_P_pcb" in pin_names hfss = add_app(project_name=self.src_project_file, design_name="uUSB", just_open=True) @@ -92,23 +92,27 @@ def test_03_add_subcircuits_hfss_link(self, add_app): assert hfss_comp.id == 87 assert hfss_comp.composed_name == "CompInst@uUSB;87;3" - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_04_refresh_dynamic_link(self): assert self.aedtapp.modeler.schematic.refresh_dynamic_link("uUSB") - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_05_set_sim_option_on_hfss_subcircuit(self): hfss_comp = "CompInst@uUSB;87;3" assert self.aedtapp.modeler.schematic.set_sim_option_on_hfss_subcircuit(hfss_comp) assert self.aedtapp.modeler.schematic.set_sim_option_on_hfss_subcircuit(hfss_comp, option="interpolate") assert not self.aedtapp.modeler.schematic.set_sim_option_on_hfss_subcircuit(hfss_comp, option="not_good") - @pytest.mark.skipif(is_linux or is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because AEDT is crashing.") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_06_set_sim_solution_on_hfss_subcircuit(self): hfss_comp = "CompInst@uUSB;87;3" assert self.aedtapp.modeler.schematic.set_sim_solution_on_hfss_subcircuit(hfss_comp) - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_07_create_page_port_and_interface_port(self): hfss_comp_id = 87 hfss3Dlayout_comp_id = 86 @@ -180,7 +184,8 @@ def test_07_create_page_port_and_interface_port(self): assert "Port_remove" not in self.aedtapp.excitations - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_08_assign_excitations(self): filepath = os.path.join(local_path, "example_models", test_subfloder, "frequency_dependent_source.fds") ports_list = ["Excitation_1", "Excitation_2"] @@ -205,7 +210,8 @@ def test_09_setup(self): LNA_setup.props["SweepDefinition"]["Data"] = " ".join(sweep_list) assert LNA_setup.update() - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_10_q2d_link(self, add_app): self.aedtapp.insert_design("test_link") q2d = add_app(application=Q2d, project_name=self.q3d, just_open=True) @@ -215,7 +221,8 @@ def test_10_q2d_link(self, add_app): assert c1.parameters["Length"] == "25mm" assert c1.parameters["r1"] == "0.3mm" - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_10_q3d_link(self, add_app): q3d = add_app(application=Q3d, project_name=self.q3d, just_open=True) @@ -225,7 +232,8 @@ def test_10_q3d_link(self, add_app): assert q3d_comp assert len(q3d_comp.pins) == 4 - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_10_hfss_link(self, add_app): hfss = add_app(project_name=self.q3d, just_open=True) @@ -237,7 +245,8 @@ def test_10_hfss_link(self, add_app): hfss2, solution_name="Setup2 : Sweep", tline_port="1" ) - @pytest.mark.skipif(is_ironpython or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(is_ironpython, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config["NonGraphical"] and is_linux, reason="Method not working in Linux and Non graphical") def test_11_siwave_link(self): model = os.path.join(local_path, "example_models", test_subfloder, "Galileo_um.siw") model_out = self.local_scratch.copyfile(model) @@ -248,7 +257,7 @@ def test_11_siwave_link(self): assert siw_comp assert len(siw_comp.pins) == 2 - @pytest.mark.skipif(config.get("skip_circuits", False) or is_linux, reason="Skipped because Desktop is crashing") + @pytest.mark.skipif(config.get("skip_circuits", False), reason="Skipped because Desktop is crashing") def test_12_create_interface_port(self): page_port = self.aedtapp.modeler.components.create_page_port(name="Port12", location=[0, -0.50]) interface_port = self.aedtapp.modeler.components.create_interface_port(name="Port12", location=[0.3, -0.50]) diff --git a/_unittest/test_28_Maxwell3D.py b/_unittest/test_28_Maxwell3D.py index 281e2fbe4b9..d60dbd56748 100644 --- a/_unittest/test_28_Maxwell3D.py +++ b/_unittest/test_28_Maxwell3D.py @@ -198,6 +198,7 @@ def test_05a_assign_coil(self): def test_05_draw_region(self): assert self.aedtapp.modeler.create_air_region(*[300] * 6) + @pytest.mark.skipif(desktop_version == "2024.2", reason="GetDisplacementCurrent not working in 2024.2") def test_06_eddycurrent(self): assert self.aedtapp.eddy_effects_on(["Plate"], enable_eddy_effects=True) oModule = self.aedtapp.odesign.GetModule("BoundarySetup") @@ -868,7 +869,10 @@ def test_53_assign_layout_force(self, layout_comp): nets_layers = {"1V0": "Bottom Solder"} assert layout_comp.assign_layout_force(nets_layers, "LC1_1") - @pytest.mark.skipif(desktop_version < "2023.2", reason="Method available in beta from 2023.2") + @pytest.mark.skipif( + desktop_version < "2023.2" or is_linux, reason="Method is available in beta in 2023.2 and later." + ) + @pytest.mark.skipif(is_linux, reason="EDB object is not loaded.") def test_54_enable_harmonic_force_layout(self, layout_comp): comp = layout_comp.modeler.user_defined_components["LC1_1"] layers = list(comp.layout_component.layers.keys()) diff --git a/_unittest/test_30_Q2D.py b/_unittest/test_30_Q2D.py index 61683934358..ec88160bca7 100644 --- a/_unittest/test_30_Q2D.py +++ b/_unittest/test_30_Q2D.py @@ -200,79 +200,79 @@ def test_15_export_equivalent_circuit(self, add_app): assert q2d.export_equivalent_circuit(os.path.join(self.local_scratch.path, "test_export_circuit.cir")) assert not q2d.export_equivalent_circuit(os.path.join(self.local_scratch.path, "test_export_circuit.doc")) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), - setup_name="Setup1", + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), + setup="Setup1", sweep="LastAdaptive", ) assert not q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), setup_name="Setup2" + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), setup="Setup2" ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), - setup_name="Setup1", + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), + setup="Setup1", sweep="LastAdaptive", variations=["r1:0.3mm"], ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), - setup_name="Setup1", + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), + setup="Setup1", sweep="LastAdaptive", variations=[" r1 : 0.3 mm "], ) assert not q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), - setup_name="Setup1", + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), + setup="Setup1", sweep="LastAdaptive", variations="r1:0.3mm", ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix_name="Original" + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix="Original" ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix_name="Test1" + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix="Test1" ) assert not q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=2 + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=2 ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=0 + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=0 ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=1 + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=1 ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=0, - res_limit="6Mohm", ind_limit="12nH", + res_limit="6Mohm", ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), lumped_length="34mm" + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), lumped_length="34mm" ) assert not q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), lumped_length="34nounits" + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), lumped_length="34nounits" ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), rise_time_value="1e-6", rise_time_unit="s", ) assert not q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), rise_time_value="23", rise_time_unit="m", ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), file_type="WELement" + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), file_type="WELement" ) assert not q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), file_type="test" + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), file_type="test" ) assert q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), model_name=q2d_q3d + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), model=q2d_q3d ) assert not q2d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), model_name="test" + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), model="test" ) self.aedtapp.close_project(q2d.project_name, save_project=False) diff --git a/_unittest/test_41_3dlayout_modeler.py b/_unittest/test_41_3dlayout_modeler.py index 14c8a0d3c92..d2022e9578d 100644 --- a/_unittest/test_41_3dlayout_modeler.py +++ b/_unittest/test_41_3dlayout_modeler.py @@ -492,10 +492,10 @@ def test_18d_delete_setup(self): self.aedtapp.delete_setup(setup_name) assert setuptd.name not in self.aedtapp.existing_analysis_setups - def test_19A_validate(self): + def test_19a_validate(self): assert self.aedtapp.validate_full_design() - def test_19D_export_to_hfss(self): + def test_19d_export_to_hfss(self): self.aedtapp.save_project() filename = "export_to_hfss_test" filename2 = "export_to_hfss_test2" @@ -503,9 +503,11 @@ def test_19D_export_to_hfss(self): file_fullname2 = os.path.join(self.local_scratch.path, filename2) setup = self.aedtapp.get_setup(self.aedtapp.existing_analysis_setups[0]) assert setup.export_to_hfss(output_file=file_fullname) - assert setup.export_to_hfss(output_file=file_fullname2, keep_net_name=True) + if not is_linux: + # TODO: EDB failing in Linux + assert setup.export_to_hfss(output_file=file_fullname2, keep_net_name=True) - def test_19E_export_to_q3d(self): + def test_19e_export_to_q3d(self): filename = "export_to_q3d_test" file_fullname = os.path.join(self.local_scratch.path, filename) setup = self.aedtapp.get_setup(self.aedtapp.existing_analysis_setups[0]) @@ -652,9 +654,9 @@ def test_41_test_create_polygon(self): @pytest.mark.skipif(not config["use_grpc"], reason="Not running in COM mode") @pytest.mark.skipif(config["desktopVersion"] < "2023.2", reason="Working only from 2023 R2") + @pytest.mark.skipif(is_linux, reason="PyEDB is failing in Linux.") def test_42_post_processing(self, add_app): test_post1 = add_app(project_name=test_post, application=Maxwell3d, subfolder=test_subfolder) - assert test_post1.post.create_fieldplot_layers( [], "Mag_H", @@ -692,6 +694,11 @@ def test_42_post_processing(self, add_app): intrinsics={"Freq": "1GHz", "Phase": "0deg"}, plot_name="Test_Layers4", ) + assert test_post2.post.create_fieldplot_layers( + ["TOP"], + "Mag_E", + intrinsics={"Freq": "1GHz", "Phase": "0deg"}, + ) assert test_post2.post.create_fieldplot_layers( ["TOP", "UNNAMED_004"], "Mag_E", @@ -701,6 +708,7 @@ def test_42_post_processing(self, add_app): self.aedtapp.close_project(test_post2.project_name) @pytest.mark.skipif(config["desktopVersion"] < "2023.2", reason="Working only from 2023 R2") + @pytest.mark.skipif(is_linux, reason="PyEDB failing in Linux") def test_42_post_processing_3d_layout(self, add_app): test = add_app( project_name="test_post_3d_layout_solved_23R2", application=Hfss3dLayout, subfolder=test_subfolder @@ -816,6 +824,7 @@ def test_96_change_nets_visibility(self, add_app): assert not hfss3d.modeler.change_net_visibility(visible="") assert not hfss3d.modeler.change_net_visibility(visible=0) + @pytest.mark.skipif(is_linux, reason="PyEDB failing in Linux") def test_96_2_report_design(self): report = AnsysReport() report.create() diff --git a/_unittest/test_98_Icepak.py b/_unittest/test_98_Icepak.py index 212ea79b7d9..dcc16aa5c9b 100644 --- a/_unittest/test_98_Icepak.py +++ b/_unittest/test_98_Icepak.py @@ -292,6 +292,9 @@ def test_14_edit_design_settings(self): assert self.aedtapp.edit_design_settings(gravity_dir=3) assert self.aedtapp.edit_design_settings(ambtemp=20) assert self.aedtapp.edit_design_settings(ambtemp="325kel") + self.aedtapp.solution_type = "Transient" + bc = self.aedtapp.create_linear_transient_assignment("0.01cel", "5") + assert self.aedtapp.edit_design_settings(ambtemp=bc) def test_15_insert_new_icepak(self): self.aedtapp.insert_design("Solve") @@ -404,6 +407,7 @@ def test_33_create_source(self): assert self.aedtapp.create_source_power(self.aedtapp.modeler["boxSource"].top_face_z.id, input_power="2W") assert self.aedtapp.create_source_power( self.aedtapp.modeler["boxSource"].bottom_face_z.id, + input_power="0W", thermal_condtion="Fixed Temperature", temperature="28cel", ) @@ -426,6 +430,7 @@ def test_33_create_source(self): voltage_current_choice="Current", voltage_current_value="1A", ) + self.aedtapp.solution_type = "SteadyState" assert not self.aedtapp.assign_source( self.aedtapp.modeler["boxSource"].top_face_x.id, "Total Power", @@ -950,7 +955,6 @@ def test_54_assign_stationary_wall(self): thickness="0mm", material="Al-Extruded", htc=10, - htc_dataset=None, ref_temperature="AmbientTemp", ht_correlation=True, ht_correlation_type="Forced Convection", @@ -966,7 +970,7 @@ def test_54_assign_stationary_wall(self): name=None, thickness="0mm", material="Al-Extruded", - htc_dataset="ds1", + htc="ds1", ref_temperature="AmbientTemp", ht_correlation=False, ) @@ -1583,3 +1587,25 @@ def test_74_boundary_conditions_dictionaries(self): def test_75_native_component_load(self, add_app): app = add_app(application=Icepak, project_name=native_import, subfolder=test_subfolder) assert len(app.native_components) == 1 + + def test_76_design_settings(self): + d = self.aedtapp.design_settings + d["AmbTemp"] = 5 + assert d["AmbTemp"] == "5cel" + d["AmbTemp"] = "5kel" + assert d["AmbTemp"] == "5kel" + d["AmbTemp"] = {"1": "2"} + assert d["AmbTemp"] == "5kel" + d["AmbGaugePressure"] = 5 + assert d["AmbGaugePressure"] == "5n_per_meter_sq" + d["GravityVec"] = 1 + assert d["GravityVec"] == "Global::Y" + assert d["GravityDir"] == "Positive" + d["GravityVec"] = 4 + assert d["GravityVec"] == "Global::Y" + assert d["GravityDir"] == "Negative" + d["GravityVec"] = "+X" + assert d["GravityVec"] == "Global::X" + assert d["GravityDir"] == "Positive" + d["GravityVec"] = "Global::Y" + assert d["GravityVec"] == "Global::Y" diff --git a/_unittest_solvers/test_00_analyze.py b/_unittest_solvers/test_00_analyze.py index d409694e968..91b665acd5b 100644 --- a/_unittest_solvers/test_00_analyze.py +++ b/_unittest_solvers/test_00_analyze.py @@ -405,13 +405,13 @@ def test_06_m3d_harmonic_forces(self, m3dtransient): use_number_of_last_cycles=True, last_cycles_number=3, calculate_force="Harmonic") m3dtransient.save_project() - m3dtransient.analyze(m3dtransient.active_setup, cores=2) + m3dtransient.analyze(m3dtransient.active_setup, cores=2, use_auto_settings=False) assert m3dtransient.export_element_based_harmonic_force(start_frequency=1, stop_frequency=100, number_of_frequency=None) assert m3dtransient.export_element_based_harmonic_force(number_of_frequency=5) def test_07_export_maxwell_fields(self, m3dtransient): - m3dtransient.analyze(m3dtransient.active_setup, cores=2) + m3dtransient.analyze(m3dtransient.active_setup, cores=2, 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", @@ -431,6 +431,7 @@ def test_07_export_maxwell_fields(self, m3dtransient): new_setup.props = setup.props new_setup.update() + @pytest.mark.skipif(is_linux and desktop_version == "2024.1", reason="Temporary skip for SPISIM related tests") def test_08_compute_erl(self, circuit_erl): touchstone_file = circuit_erl.export_touchstone() spisim = SpiSim(touchstone_file) @@ -452,6 +453,7 @@ def test_08_compute_erl(self, circuit_erl): erl_data_3 = spisim.compute_erl(specify_through_ports=[1, 2, 3, 4]) assert erl_data_3 + @pytest.mark.skipif(is_linux and desktop_version == "2024.1", reason="Temporary skip for SPISIM related tests") def test_09a_compute_com(self, local_scratch, circuit_com): touchstone_file = circuit_com.export_touchstone() spisim = SpiSim(touchstone_file) @@ -464,6 +466,7 @@ def test_09a_compute_com(self, local_scratch, circuit_com): ) assert com + @pytest.mark.skipif(is_linux and desktop_version == "2024.1", reason="Temporary skip for SPISIM related tests") def test_09b_compute_com(self, local_scratch): com_example_file_folder = os.path.join(local_path, "example_models", test_subfolder, "com_unit_test_sparam") thru_s4p = local_scratch.copyfile(os.path.join(com_example_file_folder, "SerDes_Demo_02_Thru.s4p")) @@ -503,6 +506,7 @@ def test_09b_compute_com(self, local_scratch): ) assert com_0 and com_1 + @pytest.mark.skipif(is_linux and desktop_version == "2024.1", reason="Temporary skip for SPISIM related tests") def test_09c_compute_com(self, local_scratch): com_example_file_folder = Path(local_path) / "example_models" / test_subfolder / "com_unit_test_sparam" thru_s4p = local_scratch.copyfile(com_example_file_folder / "SerDes_Demo_02_Thru.s4p") diff --git a/_unittest_solvers/test_26_emit.py b/_unittest_solvers/test_26_emit.py index 91cfc2b9fff..bde7c4b6a38 100644 --- a/_unittest_solvers/test_26_emit.py +++ b/_unittest_solvers/test_26_emit.py @@ -7,6 +7,7 @@ import pytest from pyaedt import Emit +from pyaedt import generate_unique_project_name from pyaedt.emit_core.emit_constants import EmiCategoryFilter from pyaedt.emit_core.emit_constants import InterfererType from pyaedt.emit_core.emit_constants import ResultType @@ -373,7 +374,7 @@ def test_07_antenna_component(self, add_app): reason="Skipped on versions earlier than 2023.2", ) def test_08_revision_generation(self, add_app): - self.aedtapp = add_app(application=Emit) + self.aedtapp = add_app(application=Emit, project_name=generate_unique_project_name()) assert len(self.aedtapp.results.revisions) == 0 # place components and generate the appropriate number of revisions rad1 = self.aedtapp.modeler.components.create_component("UE - Handheld") @@ -443,7 +444,7 @@ def test_08_revision_generation(self, add_app): reason="Skipped on versions earlier than 2023.2", ) def test_09_manual_revision_access_test_getters(self, add_app): - self.aedtapp = add_app(application=Emit) + self.aedtapp = add_app(application=Emit, project_name=generate_unique_project_name()) rad1 = self.aedtapp.modeler.components.create_component("UE - Handheld") ant1 = self.aedtapp.modeler.components.create_component("Antenna") rad2 = self.aedtapp.modeler.components.create_component("Bluetooth") @@ -512,7 +513,7 @@ def test_09_manual_revision_access_test_getters(self, add_app): reason="Skipped on versions earlier than 2023.2", ) def test_10_radio_band_getters(self, add_app): - self.aedtapp = add_app(application=Emit) + self.aedtapp = add_app(application=Emit, project_name=generate_unique_project_name()) rad1, ant1 = self.aedtapp.modeler.components.create_radio_antenna("New Radio") rad2, ant2 = self.aedtapp.modeler.components.create_radio_antenna("Bluetooth Low Energy (LE)") rad3, ant3 = self.aedtapp.modeler.components.create_radio_antenna("WiFi - 802.11-2012") @@ -729,7 +730,7 @@ def test_14_version(self, add_app): reason="Skipped on versions earlier than 2023.2", ) def test_15_basic_run(self, add_app): - self.aedtapp = add_app(application=Emit) + self.aedtapp = add_app(application=Emit, project_name=generate_unique_project_name()) assert len(self.aedtapp.results.revisions) == 0 # place components and generate the appropriate number of revisions rad1 = self.aedtapp.modeler.components.create_component("UE - Handheld") @@ -811,7 +812,7 @@ def test_15_basic_run(self, add_app): reason="Skipped on versions earlier than 2024.1", ) def test_16_optimal_n_to_1_feature(self, add_app): - self.aedtapp = add_app(application=Emit) + self.aedtapp = add_app(application=Emit, project_name=generate_unique_project_name()) # place components and generate the appropriate number of revisions rad1 = self.aedtapp.modeler.components.create_component("Bluetooth") ant1 = self.aedtapp.modeler.components.create_component("Antenna") @@ -867,7 +868,7 @@ def test_16_optimal_n_to_1_feature(self, add_app): reason="Skipped on versions earlier than 2023.2", ) def test_17_availability_1_to_1(self, add_app): - self.aedtapp = add_app(application=Emit) + self.aedtapp = add_app(application=Emit, project_name=generate_unique_project_name()) # place components and generate the appropriate number of revisions rad1 = self.aedtapp.modeler.components.create_component("MD400C") ant1 = self.aedtapp.modeler.components.create_component("Antenna") @@ -1136,7 +1137,7 @@ def test_22_couplings(self, add_app): ) def test_23_result_categories(self, add_app): # set up project and run - self.aedtapp = add_app(application=Emit) + self.aedtapp = add_app(application=Emit, project_name=generate_unique_project_name()) rad1 = self.aedtapp.modeler.components.create_component("GPS Receiver") ant1 = self.aedtapp.modeler.components.create_component("Antenna") ant1.move_and_connect_to(rad1) diff --git a/_unittest_solvers/test_31_Q3D.py b/_unittest_solvers/test_31_Q3D.py index 9f071d5e61e..66b83cb7bcc 100644 --- a/_unittest_solvers/test_31_Q3D.py +++ b/_unittest_solvers/test_31_Q3D.py @@ -373,58 +373,38 @@ def test_16_export_equivalent_circuit(self, add_app): q3d["d"] = "10mm" q3d.modeler.duplicate_along_line(objid="Box1", vector=[0, "d", 0]) q3d.analyze_setup(q3d.active_setup, cores=6) - assert q3d.export_equivalent_circuit( - os.path.join(self.local_scratch.path, "test_export_circuit.cir"), variations=["d: 10mm"] - ) + assert q3d.export_equivalent_circuit(os.path.join(self.local_scratch.path, "test_export_circuit.cir"), + variations=["d: 10mm"]) assert not q3d.export_equivalent_circuit(os.path.join(self.local_scratch.path, "test_export_circuit.doc")) assert not q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), - setup_name="Setup1", - sweep="LastAdaptive", - variations=["c: 10mm", "d: 20mm"], - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), setup="Setup1", + sweep="LastAdaptive", variations=["c: 10mm", "d: 20mm"]) assert not q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), setup_name="Setup2" - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), setup="Setup2") assert not q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), - setup_name="Setup1", - sweep="Sweep1", - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), setup="Setup1", + sweep="Sweep1") assert q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix_name="Original" - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix="Original") assert q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix_name="JointTest" - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix="JointTest") assert not q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix_name="JointTest1" - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), matrix="JointTest1") assert not q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=2 - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=2) assert q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=0 - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=0) assert q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=1 - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=1) assert q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), - coupling_limit_type=0, - cond_limit="3Sie", - cap_limit="4uF", - ind_limit="9uH", - res_limit="2ohm", - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), coupling_limit_type=0, + cap_limit="4uF", ind_limit="9uH", res_limit="2ohm", cond_limit="3Sie") assert q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), model_name="test_14" - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), model="test_14") assert not q3d.export_equivalent_circuit( - file_name=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), model_name="test" - ) + output_file=os.path.join(self.local_scratch.path, "test_export_circuit.cir"), model="test") self.aedtapp.close_project(q3d.project_name, save_project=False) def test_17_export_results_q3d(self, add_app): diff --git a/doc/source/API/SetupTemplatesIcepak.rst b/doc/source/API/SetupTemplatesIcepak.rst index 0d3c46980c8..97212557adc 100644 --- a/doc/source/API/SetupTemplatesIcepak.rst +++ b/doc/source/API/SetupTemplatesIcepak.rst @@ -14,7 +14,7 @@ You can edit a setup after it is created. Here is an example: # Any property of this setup can be found on this page. setup = app.create_setup(MaxIterations=5) - +Available turbulent models are: ``"ZeroEquation"``, ``"TwoEquation"``, ``"EnhancedTwoEquation"``, ``"RNG"``, ``"EnhancedRNG"``, ``"RealizableTwoEquation"``, ``"EnhancedRealizableTwoEquation"``, ``"SpalartAllmaras"``, ``"kOmegaSST"``. .. pprint:: pyaedt.modules.SetupTemplates.TransientFlowOnly .. pprint:: pyaedt.modules.SetupTemplates.TransientTemperatureOnly diff --git a/doc/source/Getting_started/Contributing.rst b/doc/source/Getting_started/Contributing.rst index c7392e86e40..b62237fa983 100644 --- a/doc/source/Getting_started/Contributing.rst +++ b/doc/source/Getting_started/Contributing.rst @@ -41,12 +41,13 @@ for switching from viewing the documentation for the latest stable release to viewing the documentation for the development version or previously released versions. -Adhere to code style --------------------- -PyAEDT is compliant with `PyAnsys code style -`_. It uses the tool -`pre-commit `_ to check the code style. You can install -and activate this tool with: +Code style +---------- +PyAEDT complies with the `PyAnsys code style +`_. +`pre-commit `_ is applied within the CI/CD to ensure compliance. +The ``pre-commit`` Python package can be installed +and run as follows: .. code:: bash @@ -73,6 +74,56 @@ For example:: Validate GitHub Workflows................................................Passed blacken-docs.............................................................Passed +Naming conventions +~~~~~~~~~~~~~~~~~~ +Consistency of names helps improve readability and +ease of use. Starting with release 0.8 a concerted effort +has been made to +improve consistency of naming and adherence to +:ref:`PEP-8`_. + +For example, methods used to create or access entities in +AEDT require that a name be passed to the method or function +as an argument. +It is tempting to +include context as part of that variable name. For example, while it is tempting to use +``setupname`` +as an argument to :meth:`Hfss.create_setup`_, +the context "setup" is +explicitly defined by the method name. The variable ``name`` provides +a more compact +description of the variable in this context. + +In previous PyAEDT versions, you can also find both ``setup_name`` and ``setupname`` used +for various methods or classes. +Improving naming consistency improves maintainability and readability. + +The following table illustrates the recommended conventions: + +.. list-table:: Keywords and object names + :widths: 25 25 50 + :header-rows: 1 + + * - Old name + - New name + - Example + * - ``setupname``, ``setup_name``, ``sweepname`` + - ``name`` + - ``Hfss.create_setup()``, ``Hfss.create_linear_step_sweep()`` + * - ``usethickness`` + - ``thickness`` + - ``Hfss.assign_coating()`` + * - ``entities`` + - ``assignment`` + - ``Maxwell.assign_current_density()`` + * - ``entity_list`` + - ``assignment`` + - ``Maxwell.assign_symmetry()`` + +Take care to use descriptive names for +variables and classes that adhere to PEP-8 and are consistent with conventions already +used in PyAEDT. + Log errors ~~~~~~~~~~ PyAEDT has an internal logging tool named ``Messenger`` diff --git a/doc/source/Resources/PyAEDTInstallerFromDesktop.py b/doc/source/Resources/PyAEDTInstallerFromDesktop.py index 8513222d0d9..7d857c83b6c 100644 --- a/doc/source/Resources/PyAEDTInstallerFromDesktop.py +++ b/doc/source/Resources/PyAEDTInstallerFromDesktop.py @@ -60,10 +60,10 @@ def run_pyinstaller_from_c_python(oDesktop): # enable in debu mode # f.write("import sys\n") # f.write('sys.path.insert(0, r"c:\\ansysdev\\git\\repos\\pyaedt")\n') - f.write("from pyaedt.misc.aedtlib_personalib_install import add_pyaedt_to_aedt\n") + f.write("from pyaedt.workflows.installer.pyaedt_installer import add_pyaedt_to_aedt\n") f.write( - 'add_pyaedt_to_aedt(aedt_version="{}", is_student_version={}, use_sys_lib=False, new_desktop_session=False, pers_dir=r"{}")\n'.format( - oDesktop.GetVersion()[:6], is_student_version(oDesktop), oDesktop.GetPersonalLibDirectory())) + 'add_pyaedt_to_aedt(aedt_version="{}", student_version={}, new_desktop_session=False)\n'.format( + oDesktop.GetVersion()[:6], is_student_version(oDesktop))) command = r'"{}" "{}"'.format(python_exe, python_script) oDesktop.AddMessage("", "", 0, command) @@ -119,6 +119,14 @@ def install_pyaedt(): if args.version < "232": ld_library_path_dirs_to_add.append("{}/Delcross".format(args.edt_root)) os.environ["LD_LIBRARY_PATH"] = ":".join(ld_library_path_dirs_to_add) + ":" + os.getenv("LD_LIBRARY_PATH", "") + os.environ["TK_LIBRARY"] = ("{}/commonfiles/CPython/{}/linx64/Release/python/lib/tk8.5". + format(args.edt_root, + args.python_version.replace( + ".", "_"))) + os.environ["TCL_LIBRARY"] = ("{}/commonfiles/CPython/{}/linx64/Release/python/lib/tcl8.5". + format(args.edt_root, + args.python_version.replace( + ".", "_"))) if not os.path.exists(venv_dir): @@ -139,7 +147,8 @@ def install_pyaedt(): zip_ref.extractall(unzipped_path) run_command( - '"{}" install --no-cache-dir --no-index --find-links={} pyaedt[all,dotnet]'.format(pip_exe, unzipped_path)) + '"{}" install --no-cache-dir --no-index --find-links={} pyaedt[all,dotnet]'.format(pip_exe, + unzipped_path)) run_command( '"{}" install --no-cache-dir --no-index --find-links={} jupyterlab'.format(pip_exe, unzipped_path)) @@ -147,14 +156,11 @@ def install_pyaedt(): run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) run_command('"{}" --default-timeout=1000 install wheel'.format(pip_exe)) run_command('"{}" --default-timeout=1000 install pyaedt[all]'.format(pip_exe)) - # run_command('"{}" --default-timeout=1000 install git+https://github.com/ansys/pyaedt.git@main'.format(pip_exe)) + # run_command( + # '"{}" --default-timeout=1000 install git+https://github.com/ansys/pyaedt.git@main'.format(pip_exe)) run_command('"{}" --default-timeout=1000 install jupyterlab'.format(pip_exe)) run_command('"{}" --default-timeout=1000 install ipython -U'.format(pip_exe)) run_command('"{}" --default-timeout=1000 install ipyvtklink'.format(pip_exe)) - # User can uncomment these lines to install Pyside6 modules - # run_command('"{}" --default-timeout=1000 install pyside6==6.4.0'.format(pip_exe)) - # run_command('"{}" --default-timeout=1000 install pyqtgraph'.format(pip_exe)) - # run_command('"{}" --default-timeout=1000 install qdarkstyle'.format(pip_exe)) if args.version == "231": run_command('"{}" uninstall -y pywin32'.format(pip_exe)) @@ -176,20 +182,6 @@ def install_pyaedt(): run_command('"{}" install --no-cache-dir --no-index --find-links={} pyaedt'.format(pip_exe, unzipped_path)) else: run_command('"{}" --default-timeout=1000 install pyaedt[all]'.format(pip_exe)) - - # if is_windows: - # pyaedt_setup_script = "{}/Lib/site-packages/pyaedt/misc/aedtlib_personalib_install.py".format(venv_dir) - # else: - # pyaedt_setup_script = "{}/lib/python{}/site-packages/pyaedt/misc/aedtlib_personalib_install.py".format( - # venv_dir, args.python_version) - # - # if not os.path.isfile(pyaedt_setup_script): - # sys.exit("[ERROR] PyAEDT was not setup properly since {} file does not exist.".format(pyaedt_setup_script)) - # - # command = '"{}" "{}" --version={}'.format(python_exe, pyaedt_setup_script, args.version) - # if args.student: - # command += " --student" - # run_command(command) sys.exit(0) diff --git a/doc/source/conf.py b/doc/source/conf.py index 94f2b01248f..4629c5687cc 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -129,7 +129,6 @@ def setup(app): "sphinx.ext.coverage", "sphinx_copybutton", "sphinx_design", - "sphinx_jinja", "sphinx.ext.graphviz", "sphinx.ext.mathjax", "sphinx.ext.inheritance_diagram", @@ -296,24 +295,6 @@ def setup(app): # "set_plot_theme('document')"), } -jinja_contexts = { - "main_toctree": { - "run_examples": config["run_examples"], - }, -} -# def prepare_jinja_env(jinja_env) -> None: -# """ -# Customize the jinja env. -# -# Notes -# ----- -# See https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment -# """ -# jinja_env.globals["project_name"] = project -# -# -# autoapi_prepare_jinja_env = prepare_jinja_env - # -- Options for HTML output ------------------------------------------------- html_short_title = html_title = "PyAEDT" html_theme = "ansys_sphinx_theme" diff --git a/doc/source/index.rst b/doc/source/index.rst index 2174585ef76..e2c60197713 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -6,9 +6,13 @@ PyAEDT documentation |version| `Source Repository `_ | `Issues `_ -PyAEDT is a Python library that interacts directly with the Ansys Electronics Desktop (AEDT) API, +PyAEDT is a Python client library that interacts directly with the Ansys Electronics Desktop (AEDT) API, enabling straightforward and efficient automation in your workflow. +.. note:: + Also consider viewing the `PyEDB documentation `_. + PyEDB is a Python client library for processing complex and large layout designs in the Ansys + Electronics Database (EDB) format, which stores information describing designs for AEDT. .. grid:: 2 @@ -26,49 +30,31 @@ enabling straightforward and efficient automation in your workflow. .. grid:: 2 - .. grid-item-card:: AEDT API reference :fa:`book-bookmark` + .. grid-item-card:: API reference :fa:`book-bookmark` :link: API/index :link-type: doc This section contains descriptions of the functions and modules included in PyAEDT. - It describes how the methods work and the parameter that can be used. + It describes how the methods work and the parameters that can be used. - .. grid-item-card:: EDB API reference :fa:`book-bookmark` - :link: https://edb.docs.pyansys.com/version/stable/ - :link-type: url - - Contains descriptions of the functions and modules included in PyEDB. - It describes how the methods work and the parameter that can be used. - -.. jinja:: main_toctree - - .. grid:: 2 - - {% if run_examples %} - .. grid-item-card:: Examples :fa:`scroll` - :link: examples/index - :link-type: doc - - Explore examples that show how to use PyAEDT to perform different types of simulations. - - {% endif %} - - .. grid-item-card:: Contribute :fa:`people-group` - :link: Getting_started/Contributing - :link-type: doc + .. grid-item-card:: Examples :fa:`scroll` + :link: examples/index + :link-type: doc - Learn how to contribute to the PyAEDT codebase or documentation. + Explore examples that show how to use PyAEDT to perform different types of simulations. -.. jinja:: main_toctree +.. grid:: 2 - .. toctree:: - :hidden: + .. grid-item-card:: Contribute :fa:`people-group` + :link: Getting_started/Contributing + :link-type: doc - Getting_started/index - User_guide/index - API/index - {% if run_examples %} - examples/index - {% endif %} + Learn how to contribute to the PyAEDT codebase or documentation. +.. toctree:: + :hidden: + Getting_started/index + User_guide/index + API/index + examples/index diff --git a/examples/03-Maxwell/Maxwell2D_Electrostatic.py b/examples/03-Maxwell/Maxwell2D_Electrostatic.py index 6eb50df7baf..3f044ffd9bc 100644 --- a/examples/03-Maxwell/Maxwell2D_Electrostatic.py +++ b/examples/03-Maxwell/Maxwell2D_Electrostatic.py @@ -198,13 +198,6 @@ M2D.post.export_field_plot(plot_name="LineTracesTest", output_dir=M2D.toolkit_directory, file_format="fldplt") -########################################################## -# Export a field plot to an image file -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Export the flux lines plot to an image file using PyVista Python package. - -M2D.post.plot_field_from_fieldplot(plot.name, show=False) - ########################################################## # Export the mesh field plot # ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/03-Maxwell/Maxwell2D_Transformer_LL.py b/examples/03-Maxwell/Maxwell2D_Transformer_LL.py new file mode 100644 index 00000000000..af134dc8802 --- /dev/null +++ b/examples/03-Maxwell/Maxwell2D_Transformer_LL.py @@ -0,0 +1,274 @@ +""" +Transformer leakage inductance calculation in Maxwell 2D Magnetostatic +---------------------------------------------------------------------- +This example shows how you can use pyAEDT to create a Maxwell 2D +magnetostatic analysis to calculate transformer leakage +inductance and reactance. +The analysis based on this document form page 8 on: +https://www.ee.iitb.ac.in/~fclab/FEM/FEM1.pdf +""" + +########################################################## +# Perform required imports +# ~~~~~~~~~~~~~~~~~~~~~~~~ + +import tempfile +from pyaedt import Maxwell2d + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") + +################################## +# Initialize and launch Maxwell 2D +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Initialize and launch Maxwell 2D, providing the version, path to the project, and the design +# name and type. + +non_graphical = False + +project_name = "Transformer_leakage_inductance" +design_name = "1 Magnetostatic" +solver = "MagnetostaticXY" +desktop_version = "2024.1" + +m2d = Maxwell2d(specified_version=desktop_version, + new_desktop_session=False, + designname=design_name, + projectname=project_name, + solution_type=solver, + non_graphical=non_graphical) + +######################### +# Initialize dictionaries +# ~~~~~~~~~~~~~~~~~~~~~~~ +# Initialize dictionaries that contain all the definitions for the design variables. + +mod = m2d.modeler +mod.model_units = "mm" + +dimensions = { + "core_width": "1097mm", + "core_height": "2880mm", + "core_opening_x1": "270mm", + "core_opening_x2": "557mm", + "core_opening_y1": "540mm", + "core_opening_y2": "2340mm", + "core_opening_width": "core_opening_x2-core_opening_x1", + "core_opening_height": "core_opening_y2-core_opening_y1", + "LV_x1": "293mm", + "LV_x2": "345mm", + "LV_width": "LV_x2-LV_x1", + "LV_mean_radius": "LV_x1+LV_width/2", + "LV_mean_turn_length": "pi*2*LV_mean_radius", + "LV_y1": "620mm", + "LV_y2": "2140mm", + "LV_height": "LV_y2-LV_y1", + "HV_x1": "394mm", + "HV_x2": "459mm", + "HV_width": "HV_x2-HV_x1", + "HV_mean_radius": "HV_x1+HV_width/2", + "HV_mean_turn_length": "pi*2*HV_mean_radius", + "HV_y1": "620mm", + "HV_y2": "2140mm", + "HV_height": "HV_y2-HV_y1", + "HV_LV_gap_radius": "(LV_x2 + HV_x1)/2", + "HV_LV_gap_length": "pi*2*HV_LV_gap_radius", +} + +specifications = { + "Amp_turns": "135024A", + "Frequency": "50Hz", + "HV_turns": "980", + "HV_current": "Amp_turns/HV_turns", +} + +#################################### +# Define variables from dictionaries +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Define design variables from the created dictionaries. + +m2d.variable_manager.set_variable(variable_name="Dimensions") + +for k, v in dimensions.items(): + m2d[k] = v + +m2d.variable_manager.set_variable(variable_name="Windings") + +for k, v in specifications.items(): + m2d[k] = v + +########################## +# Create design geometries +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# Create transformer core, HV and LV windings, and the region. + +core_id = mod.create_rectangle( + position=[0, 0, 0], + dimension_list=["core_width", "core_height", 0], + name="core", + matname="steel_1008", +) + +core_hole_id = mod.create_rectangle( + position=["core_opening_x1", "core_opening_y1", 0], + dimension_list=["core_opening_width", "core_opening_height", 0], + name="core_hole", +) + +mod.subtract(blank_list=[core_id], tool_list=[core_hole_id], keep_originals=False) + +lv_id = mod.create_rectangle( + position=["LV_x1", "LV_y1", 0], + dimension_list=["LV_width", "LV_height", 0], + name="LV", + matname="copper", +) + +hv_id = mod.create_rectangle( + position=["HV_x1", "HV_y1", 0], + dimension_list=["HV_width", "HV_height", 0], + name="HV", + matname="copper", +) + +# Very small region is enough, because all the flux is concentrated in the core +region_id = mod.create_region( + pad_percent=[20, 10, 0, 10] +) + +########################### +# Assign boundary condition +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Assign vector potential to zero on all region boundaries. This makes x=0 edge a symmetry boundary. + +region_edges = region_id.edges + +m2d.assign_vector_potential( + input_edge=region_edges, + bound_name="VectorPotential1" +) + +############################## +# Create initial mesh settings +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Assign a relatively dense mesh to all objects to ensure that the energy is calculated accurately. + +m2d.mesh.assign_length_mesh( + names=["core", "Region", "LV", "HV"], + maxlength=50, + maxel=None, + meshop_name="all_objects" +) + +#################### +# Define excitations +# ~~~~~~~~~~~~~~~~~~ +# Assign the same current in amp-turns but in opposite directions to HV and LV windings. + +m2d.assign_current( + object_list=lv_id, + amplitude="Amp_turns", + name="LV" +) +m2d.assign_current( + object_list=hv_id, + amplitude="Amp_turns", + name="HV", + swap_direction=True +) + +############################## +# Create and analyze the setup +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create and analyze the setup. Setu no. of minimum passes to 3 to ensure accuracy. + +m2d.create_setup( + setupname="Setup1", + MinimumPasses=3 +) +m2d.analyze_setup() + + +######################################################## +# Calculate transformer leakage inductance and reactance +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Calculate transformer leakage inductance from the magnetic energy. + +field_calculator = m2d.ofieldsreporter + +field_calculator.EnterQty("Energy") +field_calculator.EnterSurf("HV") +field_calculator.CalcOp("Integrate") +field_calculator.EnterScalarFunc("HV_mean_turn_length") +field_calculator.CalcOp("*") + +field_calculator.EnterQty("Energy") +field_calculator.EnterSurf("LV") +field_calculator.CalcOp("Integrate") +field_calculator.EnterScalarFunc("LV_mean_turn_length") +field_calculator.CalcOp("*") + +field_calculator.EnterQty("Energy") +field_calculator.EnterSurf("Region") +field_calculator.CalcOp("Integrate") +field_calculator.EnterScalarFunc("HV_LV_gap_length") +field_calculator.CalcOp("*") + +field_calculator.CalcOp("+") +field_calculator.CalcOp("+") + +field_calculator.EnterScalar(2) +field_calculator.CalcOp("*") +field_calculator.EnterScalarFunc("HV_current") +field_calculator.EnterScalarFunc("HV_current") +field_calculator.CalcOp("*") +field_calculator.CalcOp("/") +field_calculator.AddNamedExpression("Leakage_inductance", "Fields") + +field_calculator.CopyNamedExprToStack("Leakage_inductance") +field_calculator.EnterScalar(2) +field_calculator.EnterScalar(3.14159265358979) +field_calculator.EnterScalarFunc("Frequency") +field_calculator.CalcOp("*") +field_calculator.CalcOp("*") +field_calculator.CalcOp("*") +field_calculator.AddNamedExpression("Leakage_reactance", "Fields") + +m2d.post.create_report( + expressions=["Leakage_inductance", "Leakage_reactance"], + report_category="Fields", + primary_sweep_variable="core_width", + plot_type="Data Table", + plotname="Transformer Leakage Inductance", +) + +###################################################################### +# Print leakage inductance and reactance values in the Message Manager +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Print leakage inductance and reactance values in the Message Manager + +m2d.logger.clear_messages() +m2d.logger.info( + "Leakage_inductance = {:.4f}H".format(m2d.post.get_scalar_field_value(quantity_name="Leakage_inductance")) +) +m2d.logger.info( + "Leakage_reactance = {:.2f}Ohm".format(m2d.post.get_scalar_field_value(quantity_name="Leakage_reactance")) +) + +###################################### +# Plot energy in the simulation domain +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Most of the energy is confined in the air between the HV and LV windings. + +object_faces = [] +for name in mod.object_names: + object_faces.extend(m2d.modeler.get_object_faces(name)) + +energy_field_overlay = m2d.post.create_fieldplot_surface( + objlist=object_faces, + quantityName="energy", + plot_name="Energy", +) + +m2d.save_project() +m2d.release_desktop() +temp_dir.cleanup() diff --git a/examples/05-Q3D/Q2D_Armoured_Cable.py b/examples/05-Q3D/Q2D_Armoured_Cable.py index 3e5781417fb..4a0426cbc4f 100644 --- a/examples/05-Q3D/Q2D_Armoured_Cable.py +++ b/examples/05-Q3D/Q2D_Armoured_Cable.py @@ -135,17 +135,17 @@ mod2D.create_coordinate_system(['c_strand_xy_coord', 'c_strand_xy_coord', '0mm'], name='CS_c_strand_1') mod2D.set_working_coordinate_system('CS_c_strand_1') -c1_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'c_strand_radius', name='c_strand_1', matname='copper') +c1_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'c_strand_radius', name='c_strand_1', material='copper') c2_id = c1_id.duplicate_along_line(vector=['0mm', '2.0*c_strand_radius', '0mm'], nclones=2) mod2D.duplicate_around_axis(c2_id, axis="Z", angle=360 / core_n_strands, clones=6) c_unite_name = mod2D.unite(q2d.get_all_conductors_names()) fill_id = mod2D.create_circle(['0mm', '0mm', '0mm'], '3*c_strand_radius', name='c_strand_fill', - matname='plastic_pp_carbon_fiber') + material='plastic_pp_carbon_fiber') fill_id.color = (255, 255, 0) xlpe_id = mod2D.create_circle(['0mm', '0mm', '0mm'], '3*c_strand_radius+' + str(core_xlpe_ins_thickness) + 'mm', name='c_strand_xlpe', - matname='plastic_pe_cable_grade') + material='plastic_pe_cable_grade') xlpe_id.color = (0, 128, 128) mod2D.set_working_coordinate_system('Global') @@ -158,7 +158,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ filling_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'filling_radius', name='Filling', - matname='plastic_pp_carbon_fiber') + material='plastic_pp_carbon_fiber') filling_id.color = (255, 255, 180) ##################################################################################### @@ -166,7 +166,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ inner_sheath_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'inner_sheath_radius', name='InnerSheath', - matname='PVC plastic') + material='PVC plastic') inner_sheath_id.color = (0, 0, 0) ##################################################################################### @@ -174,7 +174,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ arm_fill_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'armour_radius', name='ArmourFilling', - matname='plastic_pp_carbon_fiber') + material='plastic_pp_carbon_fiber') arm_fill_id.color = (255, 255, 255) ##################################################################################### @@ -182,7 +182,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ outer_sheath_id = mod2D.create_circle(['0mm', '0mm', '0mm'], 'outer_sheath_radius', name='OuterSheath', - matname='PVC plastic') + material='PVC plastic') outer_sheath_id.color = (0, 0, 0) ##################################################################################### @@ -190,9 +190,9 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ arm_strand_1_id = mod2D.create_circle(['0mm', 'armour_centre_pos', '0mm'], '1.1mm', name='arm_strand_1', - matname='steel_stainless') + material='steel_stainless') arm_strand_1_id.color = (128, 128, 64) -arm_strand_1_id.duplicate_around_axis('Z', '360deg/n_arm_strands', nclones='n_arm_strands') +arm_strand_1_id.duplicate_around_axis('Z', '360deg/n_arm_strands', clones='n_arm_strands') arm_strand_names = mod2D.get_objects_w_string('arm_strand') ##################################################################################### @@ -218,9 +218,8 @@ # ~~~~~~~~~~~~~~~~~~~~~~ lumped_length = "100m" -q2d_des_settings = q2d.design_settings() +q2d_des_settings = q2d.design_settings q2d_des_settings['LumpedLength'] = lumped_length -q2d.change_design_settings(q2d_des_settings) ########################################################## # Insert setup and frequency sweep diff --git a/examples/05-Q3D/Q3D_DC_IR.py b/examples/05-Q3D/Q3D_DC_IR.py index 6536fe911e6..1b2979d1b9e 100644 --- a/examples/05-Q3D/Q3D_DC_IR.py +++ b/examples/05-Q3D/Q3D_DC_IR.py @@ -11,7 +11,7 @@ import os import pyaedt - +from pyedb import Edb ########################################################## # Set AEDT version # ~~~~~~~~~~~~~~~~ @@ -29,7 +29,7 @@ coil = pyaedt.downloads.download_file('inductance_3d_component', 'air_coil.a3dcomp') res = pyaedt.downloads.download_file('resistors', 'Res_0402.a3dcomp') project_name = pyaedt.generate_unique_name("HSD") -output_edb = os.path.join(project_dir, project_name + '.aedb') +output_edb = os.path.join(project_dir, project_name + '_out.aedb') output_q3d = os.path.join(project_dir, project_name + '_q3d.aedt') ############################################################################### @@ -38,13 +38,13 @@ # Open the EDB project and create a cutout on the selected nets # before exporting to Q3D. -edb = pyaedt.Edb(aedb_project, edbversion=aedt_version) +edb = Edb(aedb_project, edbversion=aedt_version) edb.cutout(["1.2V_AVDLL_PLL", "1.2V_AVDDL", "1.2V_DVDDL", "NetR106_1"], ["GND"], output_aedb_path=output_edb, use_pyaedt_extent_computing=True, ) - +edb.layout_validation.disjoint_nets("GND", keep_only_main_net=True) ############################################################################### # Identify pin positions # ~~~~~~~~~~~~~~~~~~~~~~ @@ -90,8 +90,7 @@ # Save and close EDB # ~~~~~~~~~~~~~~~~~~ # Save and close EDB. Then, open EDT in HFSS 3D Layout to generate the 3D model. - -edb.save_edb() +edb.save_edb_as(output_edb) edb.close_edb() h3d = pyaedt.Hfss3dLayout(output_edb, specified_version=aedt_version, non_graphical=False, new_desktop_session=True) @@ -214,7 +213,9 @@ # ~~~~~~~~ # Compute ACL solutions and plot them. -plot1 = q3d.post.create_fieldplot_surface(q3d.modeler.get_objects_by_material("copper"), quantity=drop_name) +plot1 = q3d.post.create_fieldplot_surface(q3d.modeler.get_objects_by_material("copper"), + quantity=drop_name, + intrinsics={"Freq": "1GHz"}) q3d.post.plot_field_from_fieldplot(plot1.name, project_path=q3d.working_directory, mesh_plot=False, image_format="jpg", view="isometric", show=False, plot_cad_objs=False, log_scale=False) diff --git a/pyaedt/application/AnalysisNexxim.py b/pyaedt/application/AnalysisNexxim.py index 02271d31321..28d416f5b31 100644 --- a/pyaedt/application/AnalysisNexxim.py +++ b/pyaedt/application/AnalysisNexxim.py @@ -122,7 +122,7 @@ def push_down(self, component_name): else: out_name = component_name try: - self.oproject.SetActiveDesign(out_name) + self.desktop_class.active_design(self.oproject, out_name, self.design_type) self.__init__(projectname=self.project_name, designname=out_name) except Exception: # pragma: no cover return False @@ -139,7 +139,7 @@ def pop_up(self): """ try: parent_name = self.odesign.GetName().split(";")[1].split("/")[0] - self.oproject.SetActiveDesign(parent_name) + self.desktop_class.active_design(self.oproject, parent_name, self.design_type) self.__init__(projectname=self.project_name, designname=parent_name) except Exception: return False diff --git a/pyaedt/application/Design.py b/pyaedt/application/Design.py index d342707ea54..cf0beaa4818 100644 --- a/pyaedt/application/Design.py +++ b/pyaedt/application/Design.py @@ -8,6 +8,7 @@ from __future__ import absolute_import # noreorder +from abc import abstractmethod from collections import OrderedDict import gc import json @@ -39,6 +40,7 @@ from pyaedt.generic.LoadAEDTFile import load_entire_aedt_file from pyaedt.generic.constants import AEDT_UNITS from pyaedt.generic.constants import unit_system +from pyaedt.generic.general_methods import GrpcApiError from pyaedt.generic.general_methods import check_and_download_file from pyaedt.generic.general_methods import generate_unique_name from pyaedt.generic.general_methods import is_ironpython @@ -274,6 +276,7 @@ def __init__( self._variable_manager = VariableManager(self) self._project_datasets = [] self._design_datasets = [] + self.design_settings = DesignSettings(self) @property def desktop_class(self): @@ -1019,7 +1022,7 @@ def _find_design(self): if not self._check_design_consistency(): count_consistent_designs = 0 for des in self.design_list: - self._odesign = self._oproject.SetActiveDesign(des) + self._odesign = self.desktop_class.active_design(self.oproject, des, self.design_type) if self._check_design_consistency(): count_consistent_designs += 1 activedes = des @@ -1062,7 +1065,7 @@ def odesign(self, des_name): else: activedes, warning_msg = self._find_design() if activedes: - self._odesign = self.oproject.SetActiveDesign(activedes) + self._odesign = self.desktop_class.active_design(self.oproject, activedes, self.design_type) self.logger.info(warning_msg) self.design_solutions._odesign = self.odesign @@ -1095,7 +1098,7 @@ def oproject(self): @oproject.setter def oproject(self, proj_name=None): if not proj_name: - self._oproject = self.odesktop.GetActiveProject() + self._oproject = self.desktop_class.active_project() if self._oproject: self.logger.info( "No project is defined. Project {} exists and has been read.".format(self._oproject.GetName()) @@ -1103,7 +1106,7 @@ def oproject(self, proj_name=None): else: prj_list = self.odesktop.GetProjectList() if prj_list and proj_name in list(prj_list): - self._oproject = self.odesktop.SetActiveProject(proj_name) + self._oproject = self.desktop_class.active_project(proj_name) self._add_handler() self.logger.info("Project %s set to active.", proj_name) elif os.path.exists(proj_name) or ( @@ -1114,7 +1117,7 @@ def oproject(self, proj_name=None): path = os.path.dirname(proj_name) self.odesktop.RestoreProjectArchive(proj_name, os.path.join(path, name), True, True) time.sleep(0.5) - self._oproject = self.odesktop.GetActiveProject() + self._oproject = self.desktop_class.active_project() self._add_handler() self.logger.info( "Archive {} has been restored to project {}".format(proj_name, self._oproject.GetName()) @@ -1126,7 +1129,7 @@ def oproject(self, proj_name=None): project = proj_name[:-5] + ".aedt" if os.path.exists(project) and self.check_if_project_is_loaded(project): pname = self.check_if_project_is_loaded(project) - self._oproject = self.odesktop.SetActiveProject(pname) + self._oproject = self.desktop_class.active_project(pname) self._add_handler() self.logger.info("Project %s set to active.", pname) elif os.path.exists(project): @@ -1143,7 +1146,7 @@ def oproject(self, proj_name=None): oTool.ImportEDB(proj_name) else: oTool.ImportEDB(os.path.join(proj_name, "edb.def")) - self._oproject = self.odesktop.GetActiveProject() + self._oproject = self.desktop_class.active_project() self._oproject.Save() self._add_handler() self.logger.info( @@ -1151,13 +1154,16 @@ def oproject(self, proj_name=None): ) elif self.check_if_project_is_loaded(proj_name): pname = self.check_if_project_is_loaded(proj_name) - self._oproject = self.odesktop.SetActiveProject(pname) + self._oproject = self.desktop_class.active_project(pname) self._add_handler() self.logger.info("Project %s set to active.", pname) else: if is_project_locked(proj_name): raise RuntimeError("Project is locked. Close or remove the lock before proceeding.") self._oproject = self.odesktop.OpenProject(proj_name) + if not is_windows and settings.aedt_version: + time.sleep(1) + self.odesktop.CloseAllWindows() self._add_handler() self.logger.info("Project %s has been opened.", self._oproject.GetName()) time.sleep(0.5) @@ -1169,7 +1175,7 @@ def oproject(self, proj_name=None): if not self._oproject: new_project_list = [i for i in self.odesktop.GetProjectList() if i not in project_list] if new_project_list: - self._oproject = self.odesktop.SetActiveProject(new_project_list[0]) + self._oproject = self.desktop_class.active_project(new_project_list[0]) if proj_name.endswith(".aedt"): self._oproject.Rename(proj_name, True) elif not proj_name.endswith(".aedtz"): @@ -1182,7 +1188,7 @@ def oproject(self, proj_name=None): if not self._oproject: new_project_list = [i for i in self.odesktop.GetProjectList() if i not in project_list] if new_project_list: - self._oproject = self.odesktop.SetActiveProject(new_project_list[0]) + self._oproject = self.desktop_class.active_project(new_project_list[0]) self._add_handler() self.logger.info("Project %s has been created.", self._oproject.GetName()) @@ -3133,7 +3139,7 @@ def close_project(self, name=None, save_project=True): if self.design_type == "HFSS 3D Layout Design": self._close_edb() self.logger.info("Closing the AEDT Project {}".format(name)) - oproj = self.odesktop.SetActiveProject(name) + oproj = self.desktop_class.active_project(name) proj_path = oproj.GetPath() proj_file = os.path.join(proj_path, name + ".aedt") if save_project: @@ -3148,7 +3154,7 @@ def close_project(self, name=None, save_project=True): self._oproject = None self._odesign = None else: - self.odesktop.SetActiveProject(legacy_name) + self.desktop_class.active_project(legacy_name) AedtObjects.__init__(self, self._desktop_class, is_inherithed=True) i = 0 @@ -3323,10 +3329,14 @@ def _insert_design(self, design_type, design_name=None): new_design = self._oproject.InsertDesign( design_type, unique_design_name, self.default_solution_type, "" ) + if not is_windows and settings.aedt_version and self.design_type == "Circuit Design": + time.sleep(1) + self.odesktop.CloseAllWindows() + if new_design is None: # pragma: no cover - new_design = self.oproject.SetActiveDesign(unique_design_name) + new_design = self.desktop_class.active_design(self.oproject, unique_design_name, self.design_type) if new_design is None: - self.logger.error("Fail to create new design.") + self.logger.error("Failed to create design.") return self.logger.info("Added design '%s' of type %s.", unique_design_name, design_type) name = new_design.GetName() @@ -3452,8 +3462,11 @@ def copy_design_from(self, project_fullname, design_name, save_project=True, set proj_from.CopyDesign(design_name) # paste in the destination project and get the name self._oproject.Paste() - new_designname = self._oproject.GetActiveDesign().GetName() - if self._oproject.GetActiveDesign().GetDesignType() == "HFSS 3D Layout Design": + new_designname = self.desktop_class.active_design(self._oproject, design_type=self.design_type).GetName() + if ( + self.desktop_class.active_design(self._oproject, design_type=self.design_type).GetDesignType() + == "HFSS 3D Layout Design" + ): new_designname = new_designname[2:] # name is returned as '2;EMDesign3' # close the source project self.odesktop.CloseProject(proj_from_name) @@ -3930,7 +3943,7 @@ def design_variation(self, variation_string=None): @pyaedt_function_handler() def _assert_consistent_design_type(self, des_name): if des_name in self.design_list: - self._odesign = self._oproject.SetActiveDesign(des_name) + self._odesign = self.desktop_class.active_design(self.oproject, des_name, self.design_type) dtype = self._odesign.GetDesignType() if dtype != "RMxprt": if dtype != self._design_type: @@ -3940,7 +3953,7 @@ def _assert_consistent_design_type(self, des_name): return True elif ":" in des_name: try: - self._odesign = self._oproject.SetActiveDesign(des_name) + self._odesign = self.desktop_class.active_design(self.oproject, des_name, self.design_type) return True except Exception: return des_name @@ -4019,33 +4032,65 @@ def set_temporary_directory(self, temp_dir_path): self.odesktop.SetTempDirectory(temp_dir_path) return True - @pyaedt_function_handler() - def design_settings(self): - """Get design settings for the current AEDT app. - Returns - ------- - dict - Dictionary of valid design settings. +class DesignSettings: + """Get design settings for the current AEDT app. - References - ---------- + References + ---------- - >>> oDesign.GetChildObject("Design Settings") - """ + >>> oDesign.GetChildObject("Design Settings") + """ + + def __init__(self, app): + self._app = app + self.manipulate_inputs = None try: - design_settings = self._odesign.GetChildObject("Design Settings") - except Exception: # pragma: no cover - self.logger.error("Failed to retrieve design settings.") - return False + self.design_settings = self._app.odesign.GetChildObject("Design Settings") + except GrpcApiError: # pragma: no cover + self._app.logger.error("Failed to retrieve design settings.") + self.design_settings = None + + @property + def available_properties(self): + """Available properties names for the current design.""" + return [prop for prop in self.design_settings.GetPropNames() if not prop.endswith("/Choices")] + + def __repr__(self): + lines = ["{"] + for prop in self.available_properties: + lines.append("\t{}: {}".format(prop, self.design_settings.GetPropValue(prop))) + lines.append("}") + return "\n".join(lines) + + def __setitem__(self, key, value): + if key in self.available_properties: + if self.manipulate_inputs is not None: + value = self.manipulate_inputs.execute(key, value) + key_choices = "{}/Choices".format(key) + if key_choices in self.design_settings.GetPropNames(): + value_choices = self.design_settings.GetPropValue(key_choices) + if value not in value_choices: + self._app.logger.error( + "{} is not a valid choice. Possible choices are: {}".format(value, ", ".join(value_choices)) + ) + return False + self.design_settings.SetPropValue(key, value) + else: + self._app.logger.error("{} property is not available in design settings.".format(key)) + + def __getitem__(self, key): + if key in self.available_properties: + return self.design_settings.GetPropValue(key) + else: + self._app.logger.error("{} property is not available in design settings.".format(key)) + return None + + def __contains__(self, item): + return item in self.available_properties - prop_name_list = design_settings.GetPropNames() - design_settings_dict = {} - for prop in prop_name_list: - try: - design_settings_dict[prop] = design_settings.GetPropValue(prop) - except Exception: # pragma: no cover - self.logger.warning('Could not retrieve "{}" property value in design settings.'.format(prop)) - design_settings_dict[prop] = None - return design_settings_dict +class DesignSettingsManipulation: + @abstractmethod + def execute(self, k, v): + pass diff --git a/pyaedt/application/Variables.py b/pyaedt/application/Variables.py index 96bed7e90db..ec3e31e5432 100644 --- a/pyaedt/application/Variables.py +++ b/pyaedt/application/Variables.py @@ -16,6 +16,7 @@ from __future__ import absolute_import # noreorder from __future__ import division +import ast import os import re import types @@ -156,14 +157,17 @@ def __getitem__(self, item): if variable in key_string: found_variable = True break - assert found_variable, "Input string {} is not a key of the data dictionary.".format(variable) + if not found_variable: + raise KeyError("Input string {} is not a key of the data dictionary.".format(variable)) data_out._data[variable] = self._data[key_string] data_out._header.append(variable) return data_out @pyaedt_function_handler() def __add__(self, other): - assert self.number_of_columns == other.number_of_columns, "Inconsistent number of columns" + if self.number_of_columns != other.number_of_columns: + raise ValueError("Number of columns is inconsistent.") + # Create a new object to return, avoiding changing the original inputs new_dataset = CSVDataset() # Add empty columns to new_dataset @@ -198,7 +202,8 @@ def __iadd__(self, other): for column in other.data: self._data[column] = [] - assert self.number_of_columns == other.number_of_columns, "Inconsistent number of columns" + if self.number_of_columns != other.number_of_columns: + raise ValueError("Number of columns is inconsistent.") # Append the data from 'other' for column, row_data in other.data.items(): @@ -1056,7 +1061,7 @@ def set_variable( self._logger.clear_messages() return except Exception: - pass + self._logger.debug("Something went wrong when deleting '{}'.".format(variable_name)) else: raise Exception("Unhandled input type to the design property or project variable.") # pragma: no cover @@ -1188,7 +1193,7 @@ def delete_separator(self, separator_name): ) return True except Exception: - pass + self._logger.debug("Failed to change desktop object property.") return False @pyaedt_function_handler() @@ -1229,7 +1234,7 @@ def delete_variable(self, var_name): ] ) except Exception: # pragma: no cover - pass + self._logger.debug("Failed to change desktop object property.") else: self._cleanup_variables() return True @@ -1424,9 +1429,12 @@ def __init__( self._value = self._calculated_value # If units have been specified, check for a conflict and otherwise use the specified unit system if units: - assert not self._units, "The unit specification {} is inconsistent with the identified units {}.".format( - specified_units, self._units - ) + if self._units and self._units != specified_units: + raise RuntimeError( + "The unit specification {} is inconsistent with the identified units {}.".format( + specified_units, self._units + ) + ) self._units = specified_units if not si_value and is_number(self._value): @@ -1493,7 +1501,7 @@ def _set_prop_val(self, prop, val, n_times=10): break i += 1 except Exception: - pass + self._app.logger.debug("Failed to set property '{}' value.".format(prop)) @pyaedt_function_handler() def _get_prop_val(self, prop): @@ -1515,7 +1523,7 @@ def _get_prop_val(self, prop): name = "LocalVariables" return self._app.get_oo_object(self._aedt_obj, "{}/{}".format(name, self._variable_name)).GetPropValue(prop) except Exception: - pass + self._app.logger.debug("Failed to get property '{}' value.".format(prop)) @property def name(self): @@ -1724,7 +1732,7 @@ def expression(self, value): def numeric_value(self): """Numeric part of the expression as a float value.""" if is_array(self._value): - return list(eval(self._value)) + return list(ast.literal_eval(self._value)) try: var_obj = self._aedt_obj.GetChildObject("Variables").GetChildObject(self._variable_name) val, _ = decompose_variable_value(var_obj.GetPropEvaluatedValue("EvaluatedValue")) @@ -1818,9 +1826,12 @@ def rescale_to(self, units): """ new_unit_system = unit_system(units) - assert ( - new_unit_system == self.unit_system - ), "New unit system {0} is inconsistent with the current unit system {1}." + if new_unit_system != self.unit_system: + raise ValueError( + "New unit system {} is inconsistent with the current unit system {}.".format( + new_unit_system, self.unit_system + ) + ) self._units = units return self @@ -1891,7 +1902,9 @@ def __mul__(self, other): >>> assert result_3.unit_system == "Power" """ - assert is_number(other) or isinstance(other, Variable), "Multiplier must be a scalar quantity or a variable." + if not is_number(other) and not isinstance(other, Variable): + raise ValueError("Multiplier must be a scalar quantity or a variable.") + if is_number(other): result_value = self.numeric_value * other result_units = self.units @@ -1936,10 +1949,11 @@ def __add__(self, other): >>> assert result.unit_system == "Current" """ - assert isinstance(other, Variable), "You can only add a variable with another variable." - assert ( - self.unit_system == other.unit_system - ), "Only ``Variable`` objects with the same unit system can be added." + if not isinstance(other, Variable): + raise ValueError("You can only add a variable with another variable.") + if self.unit_system != other.unit_system: + raise ValueError("Only Variable objects with the same unit system can be added.") + result_value = self.value + other.value result_units = SI_UNITS[self.unit_system] # If the units of the two operands are different, return SI-Units @@ -1978,10 +1992,11 @@ def __sub__(self, other): >>> assert result_2.unit_system == "Current" """ - assert isinstance(other, Variable), "You can only subtract a variable from another variable." - assert ( - self.unit_system == other.unit_system - ), "Only ``Variable`` objects with the same unit system can be subtracted." + if not isinstance(other, Variable): + raise ValueError("You can only subtract a variable from another variable.") + if self.unit_system != other.unit_system: + raise ValueError("Only Variable objects with the same unit system can be subtracted.") + result_value = self.value - other.value result_units = SI_UNITS[self.unit_system] # If the units of the two operands are different, return SI-Units @@ -2023,7 +2038,9 @@ def __truediv__(self, other): >>> assert result_1.unit_system == "Current" """ - assert is_number(other) or isinstance(other, Variable), "Divisor must be a scalar quantity or a variable." + if not is_number(other) and not isinstance(other, Variable): + raise ValueError("Divisor must be a scalar quantity or a variable.") + if is_number(other): result_value = self.numeric_value / other result_units = self.units diff --git a/pyaedt/application/aedt_objects.py b/pyaedt/application/aedt_objects.py index ace9bd1e616..81fad1bd4fc 100644 --- a/pyaedt/application/aedt_objects.py +++ b/pyaedt/application/aedt_objects.py @@ -1,6 +1,9 @@ import sys +import time +from pyaedt import is_linux from pyaedt import pyaedt_function_handler +from pyaedt import settings from pyaedt.generic.desktop_sessions import _desktop_sessions @@ -371,6 +374,9 @@ def oeditor(self): if not self._oeditor: if self.design_type in ["Circuit Design", "Twin Builder", "Maxwell Circuit", "EMIT"]: 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") elif self.design_type in ["RMxprt", "RMxprtSolution"]: diff --git a/pyaedt/application/design_solutions.py b/pyaedt/application/design_solutions.py index 979ab40c3f2..8b0fa36aab9 100644 --- a/pyaedt/application/design_solutions.py +++ b/pyaedt/application/design_solutions.py @@ -1,5 +1,6 @@ import copy +from pyaedt.aedt_logger import pyaedt_logger as logger from pyaedt.generic.general_methods import pyaedt_function_handler solutions_defaults = { @@ -549,7 +550,8 @@ def __init__(self, odesign, design_type, aedt_version): self._odesign = odesign self._aedt_version = aedt_version self.model_name = model_names[design_type] - assert design_type in solutions_types, "Wrong Design Type" + if not design_type in solutions_types: + raise ValueError("Design type is not valid.") # deepcopy doesn't work on remote self._solution_options = copy.deepcopy(solutions_types[design_type]) self._design_type = design_type @@ -844,7 +846,7 @@ def solution_type(self, value): opts = "" self._odesign.SetSolutionType(self._solution_options[self._solution_type]["name"], opts) except Exception: - pass + logger.error("Failed to set solution type.") class IcepakDesignSolution(DesignSolution, object): @@ -922,7 +924,7 @@ def solution_type(self, solution_type): try: self._odesign.SetSolutionType(options) except Exception: - pass + logger.error("Failed to set solution type.") class RmXprtDesignSolution(DesignSolution, object): @@ -943,7 +945,7 @@ def solution_type(self, solution_type): self._odesign.SetDesignFlow(self._design_type, solution_type) self._solution_type = solution_type except Exception: - pass + logger.error("Failed to set design flow.") @property def design_type(self): diff --git a/pyaedt/circuit.py b/pyaedt/circuit.py index 3c09eaac547..82296a387d8 100644 --- a/pyaedt/circuit.py +++ b/pyaedt/circuit.py @@ -8,8 +8,10 @@ import os import re import shutil +import time from pyaedt import Hfss3dLayout +from pyaedt import settings from pyaedt.application.AnalysisNexxim import FieldAnalysisCircuit from pyaedt.application.analysis_hf import ScatteringMethods from pyaedt.generic import ibis_reader @@ -17,6 +19,7 @@ from pyaedt.generic.constants import unit_converter from pyaedt.generic.filesystem import search_files from pyaedt.generic.general_methods import generate_unique_name +from pyaedt.generic.general_methods import is_linux from pyaedt.generic.general_methods import open_file from pyaedt.generic.general_methods import pyaedt_function_handler from pyaedt.modules.Boundary import CurrentSinSource @@ -214,7 +217,7 @@ def create_schematic_from_netlist(self, input_file): self[ppar] = pval xpos = 0.0254 except Exception: - pass + self.logger.error("Failed to parse line '{}'.".format(line)) elif ".model" in line[:7].lower() or ".lib" in line[:4].lower(): model.append(line) if model: @@ -649,6 +652,9 @@ def get_source_pin_names( self._desktop.OpenProject(source_project_path) oSrcProject = self._desktop.SetActiveProject(source_project_name) oDesign = oSrcProject.SetActiveDesign(source_design_name) + if is_linux and settings.aedt_version == "2024.1": + time.sleep(1) + self._desktop.CloseAllWindows() tmp_oModule = oDesign.GetModule("BoundarySetup") port = None if port_selector == 1: @@ -1609,9 +1615,9 @@ def import_edb_in_circuit(self, input_dir): self.logger.error( "Failed to setup co-simulation settings, make sure the simulation setup is properly defined" ) - active_project = hfss.odesktop.SetActiveProject(hfss.project_name) + active_project = hfss.desktop_class.active_project(hfss.project_name) active_project.CopyDesign(hfss.design_name) - active_project = self.odesktop.SetActiveProject(self.project_name) + active_project = hfss.desktop_class.active_project(self.project_name) active_project.Paste() hfss_3d_layout_model = self.modeler.schematic.add_subcircuit_3dlayout(hfss.design_name) hfss.close_project(save_project=False) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 88dda626451..2e5ee69272f 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -697,9 +697,9 @@ def __getitem__(self, project_design_name): else: return None - initial_oproject = self.odesktop.GetActiveProject() + initial_oproject = self.active_project() if initial_oproject.GetName() != projectname: - self.odesktop.SetActiveProject(projectname) + self.active_project(projectname) if isinstance(project_design_name[1], int) and project_design_name[1] < len(self.design_list()): designname = self.design_list()[project_design_name[1]] @@ -710,6 +710,65 @@ def __getitem__(self, project_design_name): return get_pyaedt_app(projectname, designname, self) + @pyaedt_function_handler() + def active_design(self, project_object=None, name=None, design_type=None): + """Get the active design. + + Parameters + ---------- + project_object : optional + AEDT project object. The default is ``None``, in which case the active project is used. + + name : str, optional + Name of the design to make active. + The default is ``None``, in which case the active design is returned. + + design_type : str, optional + Name of the active design to make active. + The default is ``None``, in which case the active design is returned. + + References + ---------- + + >>> oProject.GetActiveDesign + >>> oProject.SetActiveDesign + """ + if not project_object: + project_object = self.active_project() + if not name: + active_design = project_object.GetActiveDesign() + else: + active_design = project_object.SetActiveDesign(name) + if is_linux and settings.aedt_version == "2024.1" and design_type == "Circuit Design": + time.sleep(1) + self.odesktop.CloseAllWindows() + return active_design + + @pyaedt_function_handler() + def active_project(self, name=None): + """Get the active project. + + Parameters + ---------- + name : str, optional + Name of the active project to make active. + The default is ``None``, in which case the active project is returned. + + References + ---------- + + >>> oDesktop.GetActiveProject + >>> oDesktop.SetActiveProject + """ + if not name: + active_project = self.odesktop.GetActiveProject() + else: + active_project = self.odesktop.SetActiveProject(name) + if is_linux and settings.aedt_version == "2024.1": + time.sleep(1) + self.odesktop.CloseAllWindows() + return active_project + @property def install_path(self): """Installation path for AEDT.""" @@ -1142,14 +1201,14 @@ def analyze_all(self, project=None, design=None): ``True`` when successful, ``False`` when failed. """ if not project: - oproject = self.odesktop.GetActiveProject() + oproject = self.active_project() else: - oproject = self.odesktop.SetActiveProject(project) + oproject = self.active_project(project) if oproject: if not design: oproject.AnalyzeAll() else: - odesign = oproject.SetActiveDesign(design) + odesign = self.active_design(oproject, design) if odesign: odesign.AnalyzeAll() return True @@ -1217,21 +1276,21 @@ def copy_design(self, project_name=None, design_name=None, target_project=None): ``True`` when successful, ``False`` when failed. """ if not project_name: # pragma: no cover - oproject = self.odesktop.GetActiveProject() + oproject = self.active_project() else: # pragma: no cover - oproject = self.odesktop.SetActiveProject(project_name) + oproject = self.active_project(project_name) if oproject: # pragma: no cover if not design_name: - odesign = oproject.GetActiveDesign() + odesign = self.active_design(oproject) else: - odesign = oproject.SetActiveDesign(design_name) + odesign = self.active_design(oproject, design_name) if odesign: oproject.CopyDesign(design_name) if not target_project: oproject.Paste() return True else: - oproject_target = self.odesktop.SetActiveProject(target_project) + oproject_target = self.active_project(target_project) if not oproject_target: oproject_target = self.odesktop.NewProject(target_project) oproject_target.Paste() @@ -1255,9 +1314,9 @@ def project_path(self, project_name=None): """ if not project_name: - oproject = self.odesktop.GetActiveProject() + oproject = self.active_project() else: - oproject = self.odesktop.SetActiveProject(project_name) + oproject = self.active_project(project_name) if oproject: return oproject.GetPath() return None @@ -1280,9 +1339,9 @@ def design_list(self, project=None): updateddeslist = [] if not project: - oproject = self.odesktop.GetActiveProject() + oproject = self.active_project() else: - oproject = self.odesktop.SetActiveProject(project) + oproject = self.active_project(project) if oproject: deslist = list(oproject.GetTopDesignList()) for el in deslist: @@ -1309,15 +1368,15 @@ def design_type(self, project_name=None, design_name=None): Design type. """ if not project_name: - oproject = self.odesktop.GetActiveProject() + oproject = self.active_project() else: - oproject = self.odesktop.SetActiveProject(project_name) + oproject = self.active_project(project_name) if not oproject: return "" if not design_name: - odesign = oproject.GetActiveDesign() + odesign = self.active_design(oproject) else: - odesign = oproject.SetActiveDesign(design_name) + odesign = self.active_design(oproject.design_name) if odesign: return odesign.GetDesignType() return "" @@ -1402,7 +1461,8 @@ def _exception(self, ex_value, tb_data): tblist = tb_trace[0].split("\n") self.logger.error(str(ex_value)) for el in tblist: - self.logger.error(el) + if el: + self.logger.error(el) return str(ex_value) @@ -1428,11 +1488,11 @@ def load_project(self, project_file, design_name=None): """ if os.path.splitext(os.path.split(project_file)[-1])[0] in self.project_list(): - proj = self.odesktop.SetActiveProject(os.path.splitext(os.path.split(project_file)[-1])[0]) + proj = self.active_project(os.path.splitext(os.path.split(project_file)[-1])[0]) else: proj = self.odesktop.OpenProject(project_file) if proj: - active_design = proj.GetActiveDesign() + active_design = self.active_design(proj) if design_name and design_name in proj.GetChildNames(): # pragma: no cover return self[[proj.GetName(), design_name]] elif active_design: @@ -1685,172 +1745,6 @@ def get_available_toolkits(self): return list(available_toolkits.keys()) - @pyaedt_function_handler() - def add_custom_toolkit(self, toolkit_name): # pragma: no cover - """Add toolkit to AEDT Automation Tab. - - Parameters - ---------- - toolkit_name : str - Name of toolkit to add. - - Returns - ------- - bool - """ - from pyaedt.misc.install_extra_toolkits import available_toolkits - - toolkit = available_toolkits[toolkit_name] - toolkit_name = toolkit_name.replace("_", "") - - def install(package_path, package_name=None): - executable = '"{}"'.format(sys.executable) if is_windows else sys.executable - - commands = [] - if package_path.startswith("git") and package_name: - commands.append([executable, "-m", "pip", "uninstall", "--yes", package_name]) - - commands.append([executable, "-m", "pip", "install", "--upgrade", package_path]) - - if self.aedt_version_id == "2023.1" and is_windows and "AnsysEM" in sys.base_prefix: - commands.append([executable, "-m", "pip", "uninstall", "--yes", "pywin32"]) - - for command in commands: - if is_linux: - p = subprocess.Popen(command) - else: - p = subprocess.Popen(" ".join(command)) - p.wait() - - install(toolkit["pip"], toolkit.get("package_name", None)) - import site - - packages = site.getsitepackages() - full_path = None - for pkg in packages: - if os.path.exists(os.path.join(pkg, toolkit["toolkit_script"])): - full_path = os.path.join(pkg, toolkit["toolkit_script"]) - break - if not full_path: - raise FileNotFoundError("Error finding the package.") - self.add_script_to_menu( - toolkit_name=toolkit_name, - script_path=full_path, - script_image=toolkit, - product=toolkit["installation_path"], - copy_to_personal_lib=False, - add_pyaedt_desktop_init=False, - ) - - @pyaedt_function_handler() - def add_script_to_menu( - self, - toolkit_name, - script_path, - script_image=None, - product="Project", - copy_to_personal_lib=True, - add_pyaedt_desktop_init=True, - ): - """Add a script to the ribbon menu. - - .. note:: - This method is available in AEDT 2023 R2 and later. PyAEDT must be installed - in AEDT to allow this method to run. For more information, see `Installation - `_. - - Parameters - ---------- - toolkit_name : str - Name of the toolkit to appear in AEDT. - script_path : str - Full path to the script file. The script will be moved to Personal Lib. - script_image : str, optional - Full path to the image logo (a 30x30 pixel PNG file) to add to the UI. - The default is ``None``. - product : str, optional - Product to which the toolkit applies. The default is ``"Project"``, in which case - it applies to all designs. You can also specify a product, such as ``"HFSS"``. - copy_to_personal_lib : bool, optional - Whether to copy the script to Personal Lib or link the original script. Default is ``True``. - - Returns - ------- - bool - - """ - if not os.path.exists(script_path): - self.logger.error("Script does not exists.") - return False - from pyaedt.misc.install_extra_toolkits import write_toolkit_config - - toolkit_dir = os.path.join(self.personallib, "Toolkits") - aedt_version = self.aedt_version_id - tool_dir = os.path.join(toolkit_dir, product, toolkit_name) - lib_dir = os.path.join(tool_dir, "Lib") - toolkit_rel_lib_dir = os.path.relpath(lib_dir, tool_dir) - if is_linux and aedt_version <= "2023.1": - toolkit_rel_lib_dir = os.path.join("Lib", toolkit_name) - lib_dir = os.path.join(toolkit_dir, toolkit_rel_lib_dir) - toolkit_rel_lib_dir = "../../" + toolkit_rel_lib_dir - os.makedirs(lib_dir, exist_ok=True) - os.makedirs(tool_dir, exist_ok=True) - dest_script_path = script_path - if copy_to_personal_lib: - dest_script_path = os.path.join(lib_dir, os.path.split(script_path)[-1]) - shutil.copy2(script_path, dest_script_path) - files_to_copy = ["Run_PyAEDT_Toolkit_Script"] - executable_version_agnostic = sys.executable - for file_name in files_to_copy: - src = os.path.join(pathname, "misc", file_name + ".py_build") - dst = os.path.join(tool_dir, file_name.replace("_", " ") + ".py") - if not os.path.isfile(src): - raise FileNotFoundError("File not found: {}".format(src)) - with open_file(src, "r") as build_file: - with open_file(dst, "w") as out_file: - self.logger.info("Building to " + dst) - build_file_data = build_file.read() - build_file_data = ( - build_file_data.replace("##TOOLKIT_REL_LIB_DIR##", toolkit_rel_lib_dir) - .replace("##PYTHON_EXE##", executable_version_agnostic) - .replace("##PYTHON_SCRIPT##", dest_script_path) - ) - build_file_data = build_file_data.replace(" % version", "") - out_file.write(build_file_data) - if aedt_version >= "2023.2": - if not script_image: - script_image = os.path.join(os.path.dirname(__file__), "misc", "images", "large", "pyansys.png") - write_toolkit_config(os.path.join(toolkit_dir, product), lib_dir, toolkit_name, toolkit=script_image) - self.logger.info("{} toolkit installed.".format(toolkit_name)) - return True - - @pyaedt_function_handler() - def remove_script_from_menu(self, toolkit_name, product="Project"): - """Remove a toolkit script from the menu. - - Parameters - ---------- - toolkit_name : str - Name of the toolkit to remove. - product : str, optional - Product to which the toolkit applies. The default is ``"Project"``, in which case - it applies to all designs. You can also specify a product, such as ``"HFSS"``. - - Returns - ------- - bool - """ - from pyaedt.misc.install_extra_toolkits import remove_toolkit_config - - toolkit_dir = os.path.join(self.personallib, "Toolkits") - aedt_version = self.aedt_version_id - tool_dir = os.path.join(toolkit_dir, product, toolkit_name) - shutil.rmtree(tool_dir, ignore_errors=True) - if aedt_version >= "2023.2": - remove_toolkit_config(os.path.join(toolkit_dir, product), toolkit_name) - self.logger.info("{} toolkit removed successfully.".format(toolkit_name)) - return True - @pyaedt_function_handler() def submit_job( self, diff --git a/pyaedt/emit.py b/pyaedt/emit.py index 70b37b3fa13..f67e403be6e 100644 --- a/pyaedt/emit.py +++ b/pyaedt/emit.py @@ -3,7 +3,6 @@ import warnings from pyaedt import emit_core -from pyaedt import generate_unique_project_name from pyaedt.application.Design import Design from pyaedt.emit_core.Couplings import CouplingsEmit from pyaedt.emit_core.emit_constants import EMIT_VALID_UNITS @@ -114,8 +113,6 @@ def __init__( port=0, aedt_process_id=None, ): - if projectname is None: - projectname = generate_unique_project_name() self.__emit_api_enabled = False self.results = None """Constructor for the ``FieldAnalysisEmit`` class""" diff --git a/pyaedt/emit_core/results/results.py b/pyaedt/emit_core/results/results.py index 667f45ef31b..5cc9d46b2f2 100644 --- a/pyaedt/emit_core/results/results.py +++ b/pyaedt/emit_core/results/results.py @@ -33,7 +33,7 @@ def __init__(self, emit_obj): self.revisions = [] """List of all result revisions. Only one loaded at a time""" - self.design = emit_obj.odesktop.GetActiveProject().GetActiveDesign() + self.design = emit_obj.desktop_class.active_design(emit_obj.odesktop.GetActiveProject()) """Active design for the EMIT project.""" @pyaedt_function_handler() @@ -214,7 +214,9 @@ def analyze(self): # no changes since last created revision, load it elif ( self.revisions[-1].revision_number - == self.emit_project.odesktop.GetActiveProject().GetActiveDesign().GetRevision() + == self.emit_project.desktop_class.active_design( + self.emit_project.desktop_class.active_project() + ).GetRevision() ): self.get_revision(self.revisions[-1].name) else: diff --git a/pyaedt/generic/compliance.py b/pyaedt/generic/compliance.py index 091488d7a3c..c9ff5a6ffdf 100644 --- a/pyaedt/generic/compliance.py +++ b/pyaedt/generic/compliance.py @@ -221,7 +221,7 @@ def load_project(self): self._desktop_class.logger.error("Project path has not been provided.") return False self._desktop_class.load_project(self._project_file) - project = self._desktop_class.odesktop.GetActiveProject() + project = self._desktop_class.active_project() self._project_name = project.GetName() self._output_folder = os.path.join( project.GetPath(), self._project_name + ".pyaedt", generate_unique_name(self._template_name) diff --git a/pyaedt/generic/configurations.py b/pyaedt/generic/configurations.py index 8a3170a6b60..f6ba8b0381e 100644 --- a/pyaedt/generic/configurations.py +++ b/pyaedt/generic/configurations.py @@ -1673,7 +1673,7 @@ def _export_objects_properties(self, dict_out): def _export_mesh_operations(self, dict_out): dict_out["mesh"] = {} args = ["NAME:Settings"] - args += self._app.mesh.global_mesh_region.settings.parse_settings() + args += self._app.mesh.global_mesh_region.settings.parse_settings_as_args() mop = OrderedDict({}) _arg2dict(args, mop) dict_out["mesh"]["Settings"] = mop["Settings"] @@ -1683,7 +1683,7 @@ def _export_mesh_operations(self, dict_out): args = ["NAME:Settings"] else: args = ["NAME:" + mesh.name, "Enable:=", mesh.Enable] - args += mesh.settings.parse_settings() + args += mesh.settings.parse_settings_as_args() args += getattr(mesh, "_parse_assignment_value")() args += ["UserSpecifiedSettings:=", not mesh.manual_settings] mop = OrderedDict({}) diff --git a/pyaedt/generic/design_types.py b/pyaedt/generic/design_types.py index 8d0b889d94f..cb820a104db 100644 --- a/pyaedt/generic/design_types.py +++ b/pyaedt/generic/design_types.py @@ -1,5 +1,9 @@ import re import sys +import time + +from pyaedt import is_linux +from pyaedt.generic.settings import settings # lazy imports @@ -1791,6 +1795,9 @@ def get_pyaedt_app(project_name=None, design_name=None, desktop=None): oProject = odesktop.GetActiveProject() else: oProject = odesktop.SetActiveProject(project_name) + if is_linux and settings.aedt_version == "2024.1": + time.sleep(1) + odesktop.CloseAllWindows() if not oProject: raise AttributeError("No project is present.") design_names = [] @@ -1804,6 +1811,9 @@ def get_pyaedt_app(project_name=None, design_name=None, desktop=None): oDesign = oProject.GetActiveDesign() else: oDesign = oProject.SetActiveDesign(design_name) + if is_linux and settings.aedt_version == "2024.1": + time.sleep(1) + odesktop.CloseAllWindows() if not oDesign: raise AttributeError("No design is present.") design_type = oDesign.GetDesignType() diff --git a/pyaedt/generic/general_methods.py b/pyaedt/generic/general_methods.py index a286641a2f7..5dad1863a90 100644 --- a/pyaedt/generic/general_methods.py +++ b/pyaedt/generic/general_methods.py @@ -110,7 +110,6 @@ def _exception(ex_info, func, args, kwargs, message="Type Error"): ] if any(exc in trace for exc in exceptions): continue - # if func.__name__ in trace: for el in trace.split("\n"): _write_mes(el) for trace in tb_trace: @@ -118,14 +117,10 @@ def _exception(ex_info, func, args, kwargs, message="Type Error"): continue tblist = trace.split("\n") for el in tblist: - # if func.__name__ in el: - _write_mes(el) + if el: + _write_mes(el) _write_mes("{} on {}".format(message, func.__name__)) - # try: - # _write_mes(ex_info[1].args[0]) - # except (IndexError, AttributeError): - # pass message_to_print = "" messages = "" @@ -138,7 +133,6 @@ def _exception(ex_info, func, args, kwargs, message="Type Error"): pass if "error" in messages: message_to_print = messages[messages.index("[error]") :] - # _write_mes("{} - {} - {}.".format(ex_info[1], func.__name__, message.upper())) if message_to_print: _write_mes("Last Electronics Desktop Message - " + message_to_print) @@ -253,7 +247,7 @@ def deprecate_kwargs(func_name, kwargs, aliases): msg += "{} is deprecated, use {} instead.".format(alias, new) raise TypeError(msg) pyaedt_logger.warning( - '`{}` is deprecated as an argument to `{}`; use" f" `{}` instead.'.format(alias, func_name, new) + "Argument `{}` is deprecated for method `{}`; use `{}` instead.".format(alias, func_name, new) ) kwargs[new] = kwargs.pop(alias) diff --git a/pyaedt/generic/plot.py b/pyaedt/generic/plot.py index ce95fcd9c6a..76c8a18b5ad 100644 --- a/pyaedt/generic/plot.py +++ b/pyaedt/generic/plot.py @@ -1327,7 +1327,24 @@ def _read_mesh_files(self, read_frames=False): if ".case" in field.path: reader = pv.get_reader(os.path.abspath(field.path)).read() field._cached_polydata = reader[reader.keys()[0]].extract_surface() - field.scalar_name = field._cached_polydata.point_data.active_scalars_name + + if ( + hasattr(field._cached_polydata.point_data, "active_vectors") + and field._cached_polydata.point_data.active_vectors_name + ): + field.scalar_name = field._cached_polydata.point_data.active_scalars_name + vector_scale = (max(field._cached_polydata.bounds) - min(field._cached_polydata.bounds)) / ( + 10 + * ( + np.vstack(field._cached_polydata.active_vectors).max() + - np.vstack(field._cached_polydata.active_vectors).min() + ) + ) + field._cached_polydata["vectors"] = field._cached_polydata.active_vectors * vector_scale + + field.is_vector = True + else: + field.scalar_name = field._cached_polydata.point_data.active_scalars_name elif ".aedtplt" in field.path: vertices, faces, scalars, log1 = _parse_aedtplt(field.path) diff --git a/pyaedt/generic/spisim.py b/pyaedt/generic/spisim.py index 9593205a78f..d2e2f1c1507 100644 --- a/pyaedt/generic/spisim.py +++ b/pyaedt/generic/spisim.py @@ -73,6 +73,8 @@ def _compute_spisim(self, parameter, out_file="", touchstone_file="", config_fil my_env = os.environ.copy() my_env.update(settings.aedt_environment_variables) if is_linux: # pragma: no cover + if "ANSYSEM_ROOT_PATH" not in my_env: + my_env["ANSYSEM_ROOT_PATH"] = self.desktop_install_dir command.append("&") with open_file(out_processing, "w") as outfile: subprocess.Popen(command, env=my_env, stdout=outfile, stderr=outfile).wait() # nosec diff --git a/pyaedt/hfss.py b/pyaedt/hfss.py index fd668db9f00..35e3bc7f001 100644 --- a/pyaedt/hfss.py +++ b/pyaedt/hfss.py @@ -1970,7 +1970,9 @@ def create_source_excitation(self, assignment, point1, point2, name, source_type props = OrderedDict({"Objects": [assignment], "Direction": OrderedDict({"Start": point1, "End": point2})}) return self._create_boundary(name, props, source_type) - @pyaedt_function_handler(face="assignment", nummodes="modes", portname="name", renorm="renormalize") + @pyaedt_function_handler( + face="assignment", nummodes="modes", portname="name", renorm="renormalize", deembed_dist="deembed_distance" + ) def create_floquet_port( self, assignment, diff --git a/pyaedt/hfss3dlayout.py b/pyaedt/hfss3dlayout.py index d0392ceaeed..b852dfb0531 100644 --- a/pyaedt/hfss3dlayout.py +++ b/pyaedt/hfss3dlayout.py @@ -707,8 +707,8 @@ def import_edb(self, input_folder): input_folder = os.path.join(input_folder, "edb.def") self.oimport_export.ImportEDB(input_folder) self._close_edb() - project_name = self.odesktop.GetActiveProject().GetName() - design_name = self.odesktop.GetActiveProject().GetActiveDesign().GetName().split(";")[-1] + project_name = self.desktop_class.active_project().GetName() + design_name = self.desktop_class.active_design(self.desktop_class.active_project()).GetName().split(";")[-1] self.__init__(projectname=project_name, designname=design_name) return True diff --git a/pyaedt/icepak.py b/pyaedt/icepak.py index 843f0da26b8..14f220ebec4 100644 --- a/pyaedt/icepak.py +++ b/pyaedt/icepak.py @@ -9,6 +9,7 @@ from pyaedt import is_ironpython from pyaedt import is_linux +from pyaedt.application.Design import DesignSettingsManipulation from pyaedt.generic.general_methods import GrpcApiError from pyaedt.modeler.cad.elements3d import FacePrimitive from pyaedt.modeler.geometry_operators import GeometryOperators as go @@ -23,6 +24,7 @@ from pyaedt.application.Analysis3D import FieldAnalysis3D from pyaedt.generic.DataHandlers import _arg2dict +from pyaedt.generic.DataHandlers import _dict2arg from pyaedt.generic.DataHandlers import random_string from pyaedt.generic.configurations import ConfigurationsIcepak from pyaedt.generic.general_methods import generate_unique_name @@ -174,6 +176,7 @@ def __init__( ) self._monitor = Monitor(self) self._configurations = ConfigurationsIcepak(self) + self.design_settings.manipulate_inputs = IcepakDesignSettingsManipulation(self) def _init_from_design(self, *args, **kwargs): self.__init__(*args, **kwargs) @@ -661,7 +664,7 @@ def create_source_power( thermal_dependent_dataset : str, optional Name of the dataset if a thermal dependent power source is to be assigned. The default is ``None``. input_power : str, float, or int, optional - Input power. The default is ``"0W"``. + Input power. The default is ``None``. thermal_condtion : str, optional Thermal condition. The default is ``"Total Power"``. surface_heat : str, optional @@ -1605,25 +1608,30 @@ def create_parametric_heatsink_on_face( hs.name = hs_name return hs, name_map - # fmt: off + @pyaedt_function_handler( + ambtemp="ambient_temperature", + performvalidation="perform_validation", + defaultfluid="default_fluid", + defaultsolid="default_solid", + ) @pyaedt_function_handler() def edit_design_settings( - self, - gravity_dir=0, - ambtemp=20, - performvalidation=False, - check_level="None", - defaultfluid="air", - defaultsolid="Al-Extruded", - export_monitor=False, - export_directory=os.getcwd(), - gauge_pressure=0, - radiation_temperature=20, - ignore_unclassified_objects=False, - skip_intersection_checks=False, - **kwargs + self, + gravity_dir=0, + ambient_temperature=20, + perform_validation=False, + check_level="None", + default_fluid="air", + default_solid="Al-Extruded", + default_surface="Steel-oxidised-surface", + export_monitor=False, + export_sherlock=False, + export_directory=os.getcwd(), + gauge_pressure=0, + radiation_temperature=20, + ignore_unclassified_objects=False, + skip_intersection_checks=False, ): - # fmt: on """Update the main settings of the design. Parameters @@ -1631,26 +1639,33 @@ def edit_design_settings( gravity_dir : int, optional Gravity direction from -X to +Z. Options are ``0`` to ``5``. The default is ``0``. - ambtemp : float, str, optional + ambient_temperature : float, str, BoundaryDict or dict optional Ambient temperature. The default is ``20``. - The default unit is celsius for float or string including unit definition is accepted, e.g. ``325kel``. - performvalidation : bool, optional + The default unit is Celsius for a float or string value. + You can include a unit for a string value. For example, ``325kel``. + perform_validation : bool, optional Whether to perform validation. The default is ``False``. check_level : str, optional Level of check to perform during validation. The default is ``"None"``. - defaultfluid : str, optional + default_fluid : str, optional Default fluid material. The default is ``"air"``. - defaultsolid : str, optional + default_solid : str, optional Default solid material. The default is ``"Al-Extruded"``. + default_surface : str, optional + Default surface material. The default is ``"Steel-oxidised-surface"``. export_monitor : bool, optional - Whether to use the default export directory for monitor point data. - The default value is ``False``. + Whether to export monitor data. + The default is ``False``. + export_sherlock : bool, optional + Whether to export temperature data for Sherlock. + The default is ``False``. export_directory : str, optional - Default export directory for monitor point data. The default value is the current working directory. + Default export directory for monitor point and Sherlock data. + The default is the current working directory. gauge_pressure : float, str, optional - Set the Gauge pressure. It can be a float (units will be "n_per_meter_sq") or a string with units. - Default is ``0``. + Gauge pressure. It can be a float where "n_per_meter_sq" is + assumed as the units or a string with the units specified. The default is ``0``. radiation_temperature : float, str, optional Set the radiation temperature. It can be a float (units will be "cel") or a string with units. Default is ``20``. @@ -1671,54 +1686,56 @@ def edit_design_settings( >>> oDesign.SetDesignSettings """ # - # Configure design settings for gravity etc - IceGravity = ["X", "Y", "Z"] - GVPos = False - if "gravityDir" in kwargs: # pragma: no cover - warnings.warn( - "`gravityDir` is deprecated. Use `gravity_dir` instead.", - DeprecationWarning, - ) - - gravity_dir = kwargs["gravityDir"] - if "CheckLevel" in kwargs: # pragma: no cover - warnings.warn( - "`CheckLevel` is deprecated. Use `check_level` instead.", - DeprecationWarning, - ) - - check_level = kwargs["CheckLevel"] + # Configure design settings such as gravity + ice_gravity = ["X", "Y", "Z"] + gv_pos = False if int(gravity_dir) > 2: - GVPos = True - GVA = IceGravity[int(gravity_dir) - 3] + gv_pos = True + gva = ice_gravity[int(gravity_dir) - 3] + arg1 = [ + "NAME:Design Settings Data", + "Perform Minimal validation:=", + perform_validation, + "Default Fluid Material:=", + default_fluid, + "Default Solid Material:=", + default_solid, + "Default Surface Material:=", + default_surface, + "SherlockExportOnSimulationComplete:=", + export_sherlock, + "SherlockExportAsFatigue:=", + True, + "SherlockExportDirectory:=", + export_directory, + "AmbientPressure:=", + self.value_with_units(gauge_pressure, "n_per_meter_sq"), + "AmbientRadiationTemperature:=", + self.value_with_units(radiation_temperature, "cel"), + "Gravity Vector CS ID:=", + 1, + "Gravity Vector Axis:=", + gva, + "Positive:=", + gv_pos, + "ExportOnSimulationComplete:=", + export_monitor, + "ExportDirectory:=", + export_directory, + ] + if not isinstance(ambient_temperature, (BoundaryDictionary, dict)): + arg1.append("AmbientTemperature:=") + arg1.append(self.value_with_units(ambient_temperature, "cel")) + else: + assignment = self._parse_variation_data( + "Ambient Temperature", + "Transient", + variation_value=ambient_temperature["Values"], + function=ambient_temperature["Function"], + ) + _dict2arg(assignment, arg1) self._odesign.SetDesignSettings( - [ - "NAME:Design Settings Data", - "Perform Minimal validation:=", - performvalidation, - "Default Fluid Material:=", - defaultfluid, - "Default Solid Material:=", - defaultsolid, - "Default Surface Material:=", - "Steel-oxidised-surface", - "AmbientTemperature:=", - self.value_with_units(ambtemp, "cel"), - "AmbientPressure:=", - self.value_with_units(gauge_pressure, "n_per_meter_sq"), - "AmbientRadiationTemperature:=", - self.value_with_units(radiation_temperature, "cel"), - "Gravity Vector CS ID:=", - 1, - "Gravity Vector Axis:=", - GVA, - "Positive:=", - GVPos, - "ExportOnSimulationComplete:=", - export_monitor, - "ExportDirectory:=", - export_directory, - ], + arg1, [ "NAME:Model Validation Settings", "EntityCheckLevel:=", @@ -1731,21 +1748,19 @@ def edit_design_settings( ) return True - @pyaedt_function_handler(designname="design", - setupname="setup", - sweepname="sweep", - paramlist="parameters", - object_list="assignment") + @pyaedt_function_handler( + designname="design", setupname="setup", sweepname="sweep", paramlist="parameters", object_list="assignment" + ) def assign_em_losses( - self, - design="HFSSDesign1", - setup="Setup1", - sweep="LastAdaptive", - map_frequency=None, - surface_objects=None, - source_project_name=None, - parameters=None, - assignment=None, + self, + design="HFSSDesign1", + setup="Setup1", + sweep="LastAdaptive", + map_frequency=None, + surface_objects=None, + source_project_name=None, + parameters=None, + assignment=None, ): """Map EM losses to an Icepak design. @@ -1850,13 +1865,13 @@ def assign_em_losses( @pyaedt_function_handler() def eval_surface_quantity_from_field_summary( - self, - faces_list, - quantity_name="HeatTransCoeff", - savedir=None, - filename=None, - sweep_name=None, - parameter_dict_with_values={}, + self, + faces_list, + quantity_name="HeatTransCoeff", + savedir=None, + filename=None, + sweep_name=None, + parameter_dict_with_values={}, ): """Export the field surface output. @@ -1923,13 +1938,13 @@ def eval_surface_quantity_from_field_summary( return filename def eval_volume_quantity_from_field_summary( - self, - object_list, - quantity_name="HeatTransCoeff", - savedir=None, - filename=None, - sweep_name=None, - parameter_dict_with_values={}, + self, + object_list, + quantity_name="HeatTransCoeff", + savedir=None, + filename=None, + sweep_name=None, + parameter_dict_with_values={}, ): """Export the field volume output. @@ -1993,17 +2008,17 @@ def eval_volume_quantity_from_field_summary( ) return filename + @pyaedt_function_handler(geometryType="geometry_type", variationlist="variation_list") def export_summary( - self, - output_dir=None, - solution_name=None, - type="Object", - geometry_type="Volume", - quantity="Temperature", - variation="", - variation_list=None, - filename="IPKsummaryReport", - **kwargs + self, + output_dir=None, + solution_name=None, + type="Object", + geometry_type="Volume", + quantity="Temperature", + variation="", + variation_list=None, + filename="IPKsummaryReport", ): """Export a fields summary of all objects. @@ -2040,16 +2055,6 @@ def export_summary( >>> oModule.EditFieldsSummarySetting >>> oModule.ExportFieldsSummary """ - if 'geometryType' in kwargs: - warnings.warn("The 'geometryType' argument is deprecated. Use 'geometry_type' instead.", - DeprecationWarning) - - if 'variationlist' in kwargs: - warnings.warn("The 'variationlist' argument is deprecated. Use 'variation_list' instead.", - DeprecationWarning) - - geometry_type = kwargs.get('geometryType', geometry_type) - variation_list = kwargs.get('variationlist', variation_list) if variation_list is None: variation_list = [] @@ -2201,14 +2206,14 @@ def get_link_data(self, links_data, **kwargs): @pyaedt_function_handler() def create_fan( - self, - name=None, - is_2d=False, - shape="Circular", - cross_section="XY", - radius="0.008mm", - hub_radius="0mm", - origin=None, + self, + name=None, + is_2d=False, + shape="Circular", + cross_section="XY", + radius="0.008mm", + hub_radius="0mm", + origin=None, ): """Create a fan component in Icepak that is linked to an HFSS 3D Layout object. @@ -2302,7 +2307,7 @@ def create_fan( "MaterialDefinitionParameters": OrderedDict({"VariableOrders": OrderedDict()}), "MapInstanceParameters": "DesignVariable", "UniqueDefinitionIdentifier": "57c8ab4e-4db9-4881-b6bb-" - + random_string(12, char_set="abcdef0123456789"), + + random_string(12, char_set="abcdef0123456789"), "OriginFilePath": "", "IsLocal": False, "ChecksumString": "", @@ -2336,7 +2341,7 @@ def create_fan( @pyaedt_function_handler() def create_ipk_3dcomponent_pcb( - self, + self, compName, setupLinkInfo, solutionFreq, @@ -2490,7 +2495,7 @@ def create_ipk_3dcomponent_pcb( @pyaedt_function_handler() def create_pcb_from_3dlayout( - self, + self, component_name, project_name, design_name, @@ -2644,12 +2649,12 @@ def copyGroupFrom(self, group_name, source_design, source_project_name=None, sou source_project_path = kwargs["sourceProjectPath"] if source_project_name == self.project_name or source_project_name is None: - active_project = self._desktop.GetActiveProject() + active_project = self.desktop_class.active_project() else: self._desktop.OpenProject(source_project_path) - active_project = self._desktop.SetActiveProject(source_project_name) + active_project = self.desktop_class.active_project(source_project_name) - active_design = active_project.SetActiveDesign(source_design) + active_design = self.desktop_class.active_design(active_project, source_design) active_editor = active_design.SetActiveEditor("3D Modeler") active_editor.Copy(["NAME:Selections", "Selections:=", group_name]) @@ -2660,15 +2665,15 @@ def copyGroupFrom(self, group_name, source_design, source_project_name=None, sou @pyaedt_function_handler() def globalMeshSettings( - self, - meshtype, - gap_min_elements="1", - noOgrids=False, - MLM_en=True, - MLM_Type="3D", - stairStep_en=False, - edge_min_elements="1", - object="Region", + self, + meshtype, + gap_min_elements="1", + noOgrids=False, + MLM_en=True, + MLM_Type="3D", + stairStep_en=False, + edge_min_elements="1", + object="Region", ): """Create a custom mesh tailored on a PCB design. @@ -2757,7 +2762,7 @@ def globalMeshSettings( @pyaedt_function_handler() def create_meshregion_component( - self, scale_factor=1.0, name="Component_Region", restore_padding_values=[50, 50, 50, 50, 50, 50] + self, scale_factor=1.0, name="Component_Region", restore_padding_values=[50, 50, 50, 50, 50, 50] ): """Create a bounding box to use as a mesh region in Icepak. @@ -2904,14 +2909,14 @@ def get_gas_objects(self): @pyaedt_function_handler() def generate_fluent_mesh( - self, - object_lists=None, - meshtype="tetrahedral", - min_size=None, - max_size=None, - inflation_layer_number=3, - inflation_growth_rate=1.2, - mesh_growth_rate=1.2, + self, + object_lists=None, + meshtype="tetrahedral", + min_size=None, + max_size=None, + inflation_layer_number=3, + inflation_growth_rate=1.2, + mesh_growth_rate=1.2, ): """Generate a Fluent mesh for a list of selected objects and assign the mesh automatically to the objects. @@ -3070,16 +3075,19 @@ def generate_fluent_mesh( @pyaedt_function_handler() def apply_icepak_settings( - self, - ambienttemp=20, - gravityDir=5, - perform_minimal_val=True, - default_fluid="air", - default_solid="Al-Extruded", - default_surface="Steel-oxidised-surface", + self, + ambienttemp=20, + gravityDir=5, + perform_minimal_val=True, + default_fluid="air", + default_solid="Al-Extruded", + default_surface="Steel-oxidised-surface", ): """Apply Icepak default design settings. + .. deprecated:: 0.8.9 + Use the ``edit_design_settins()`` method. + Parameters ---------- ambienttemp : float, str, optional @@ -3107,40 +3115,16 @@ def apply_icepak_settings( >>> oDesign.SetDesignSettings """ - ambient_temperature = self.modeler._arg_with_dim(ambienttemp, "cel") - - axes = ["X", "Y", "Z"] - GVPos = False - if int(gravityDir) > 2: - GVPos = True - gravity_axis = axes[int(gravityDir) - 3] - self.odesign.SetDesignSettings( - [ - "NAME:Design Settings Data", - "Perform Minimal validation:=", - perform_minimal_val, - "Default Fluid Material:=", - default_fluid, - "Default Solid Material:=", - default_solid, - "Default Surface Material:=", - default_surface, - "AmbientTemperature:=", - ambient_temperature, - "AmbientPressure:=", - "0n_per_meter_sq", - "AmbientRadiationTemperature:=", - ambient_temperature, - "Gravity Vector CS ID:=", - 1, - "Gravity Vector Axis:=", - gravity_axis, - "Positive:=", - GVPos, - ], - ["NAME:Model Validation Settings"], + + warnings.warn("Use the ``edit_design_settings()`` method.", DeprecationWarning) + return self.edit_design_settings( + ambient_temperature=ambienttemp, + gravity_dir=gravityDir, + perform_validation=perform_minimal_val, + default_fluid=default_fluid, + default_solid=default_solid, + default_surface=default_surface, ) - return True @pyaedt_function_handler() def assign_surface_material(self, obj, mat): @@ -3204,31 +3188,31 @@ def assign_surface_material(self, obj, mat): @pyaedt_function_handler() def import_idf( - self, - board_path, - library_path=None, - control_path=None, - filter_cap=False, - filter_ind=False, - filter_res=False, - filter_height_under=None, - filter_height_exclude_2d=False, - power_under=None, - create_filtered_as_non_model=False, - high_surface_thick="0.07mm", - low_surface_thick="0.07mm", - internal_thick="0.07mm", - internal_layer_number=2, - high_surface_coverage=30, - low_surface_coverage=30, - internal_layer_coverage=30, - trace_material="Cu-Pure", - substrate_material="FR-4", - create_board=True, - model_board_as_rect=False, - model_device_as_rect=True, - cutoff_height="5mm", - component_lib="", + self, + board_path, + library_path=None, + control_path=None, + filter_cap=False, + filter_ind=False, + filter_res=False, + filter_height_under=None, + filter_height_exclude_2d=False, + power_under=None, + create_filtered_as_non_model=False, + high_surface_thick="0.07mm", + low_surface_thick="0.07mm", + internal_thick="0.07mm", + internal_layer_number=2, + high_surface_coverage=30, + low_surface_coverage=30, + internal_layer_coverage=30, + trace_material="Cu-Pure", + substrate_material="FR-4", + create_board=True, + model_board_as_rect=False, + model_device_as_rect=True, + cutoff_height="5mm", + component_lib="", ): """Import an IDF file into an Icepak design. @@ -3297,7 +3281,7 @@ def import_idf( >>> oDesign.ImportIDF """ - active_design_name = self.oproject.GetActiveDesign().GetName() + active_design_name = self.desktop_class.active_design(self.oproject).GetName() if not library_path: if board_path.endswith(".emn"): library_path = board_path[:-3] + "emp" @@ -3389,7 +3373,7 @@ def import_idf( ) self.modeler.add_new_objects() if active_design_name: - self.oproject.SetActiveDesign(active_design_name) + self.desktop_class.active_design(self.oproject, active_design_name) return True @pyaedt_function_handler() @@ -3506,35 +3490,34 @@ def get_face_normal(obj_face): return boundary return None - @pyaedt_function_handler() + @pyaedt_function_handler(htc_dataset="htc") def assign_stationary_wall( - self, - geometry, - boundary_condition, - name=None, - temperature="0cel", - heat_flux="0irrad_W_per_m2", - thickness="0mm", - htc="0w_per_m2kel", - ref_temperature="AmbientTemp", - material="Al-Extruded", # relevant if th>0 - radiate=False, - radiate_surf_mat="Steel-oxidised-surface", # relevant if radiate = False - ht_correlation=False, - ht_correlation_type="Natural Convection", - ht_correlation_fluid="air", - ht_correlation_flow_type="Turbulent", - ht_correlation_flow_direction="X", - ht_correlation_value_type="Average Values", # "Local Values" - ht_correlation_free_stream_velocity="1m_per_sec", - ht_correlation_surface="Vertical", # Top, Bottom, Vertical - ht_correlation_amb_temperature="AmbientTemp", - shell_conduction=False, - ext_surf_rad=False, - ext_surf_rad_material="Stainless-steel-cleaned", - ext_surf_rad_ref_temp="AmbientTemp", - ext_surf_rad_view_factor="1", - **kwargs + self, + geometry, + boundary_condition, + name=None, + temperature="0cel", + heat_flux="0irrad_W_per_m2", + thickness="0mm", + htc="0w_per_m2kel", + ref_temperature="AmbientTemp", + material="Al-Extruded", # relevant if th>0 + radiate=False, + radiate_surf_mat="Steel-oxidised-surface", # relevant if radiate = False + ht_correlation=False, + ht_correlation_type="Natural Convection", + ht_correlation_fluid="air", + ht_correlation_flow_type="Turbulent", + ht_correlation_flow_direction="X", + ht_correlation_value_type="Average Values", # "Local Values" + ht_correlation_free_stream_velocity="1m_per_sec", + ht_correlation_surface="Vertical", # Top, Bottom, Vertical + ht_correlation_amb_temperature="AmbientTemp", + shell_conduction=False, + ext_surf_rad=False, + ext_surf_rad_material="Stainless-steel-cleaned", + ext_surf_rad_ref_temp="AmbientTemp", + ext_surf_rad_view_factor="1", ): """Assign surface wall boundary condition. @@ -3683,19 +3666,11 @@ def assign_stationary_wall( props["Thickness"] = (thickness,) props["Solid Material"] = material props["External Condition"] = boundary_condition - if "htc_dataset" in kwargs: # backward compatibility - warnings.warn("``htc_dataset`` argument is being deprecated. Create a dictionary as per" - "documentation and assign it to the ``htc`` argument.", DeprecationWarning) - if kwargs["htc_dataset"] is not None: - htc = {"Type": "Temp Dep", - "Function": "Piecewise Linear", - "Values": kwargs["htc_dataset"], - } for quantity, assignment_value, to_add in [ ("External Radiation Reference Temperature", ext_surf_rad_ref_temp, ext_surf_rad), ("Heat Transfer Coefficient", htc, boundary_condition == "Heat Transfer Coefficient"), ("Temperature", temperature, boundary_condition == "Temperature"), - ("Heat Flux", heat_flux, boundary_condition == "Heat Flux") + ("Heat Flux", heat_flux, boundary_condition == "Heat Flux"), ]: if to_add: if isinstance(assignment_value, (dict, BoundaryDictionary)): @@ -3748,15 +3723,15 @@ def assign_stationary_wall( @pyaedt_function_handler() def assign_stationary_wall_with_heat_flux( - self, - geometry, - name=None, - heat_flux="0irrad_W_per_m2", - thickness="0mm", - material="Al-Extruded", - radiate=False, - radiate_surf_mat="Steel-oxidised-surface", - shell_conduction=False, + self, + geometry, + name=None, + heat_flux="0irrad_W_per_m2", + thickness="0mm", + material="Al-Extruded", + radiate=False, + radiate_surf_mat="Steel-oxidised-surface", + shell_conduction=False, ): """Assign a surface wall boundary condition with specified heat flux. @@ -3810,15 +3785,15 @@ def assign_stationary_wall_with_heat_flux( @pyaedt_function_handler() def assign_stationary_wall_with_temperature( - self, - geometry, - name=None, - temperature="0cel", - thickness="0mm", - material="Al-Extruded", - radiate=False, - radiate_surf_mat="Steel-oxidised-surface", - shell_conduction=False, + self, + geometry, + name=None, + temperature="0cel", + thickness="0mm", + material="Al-Extruded", + radiate=False, + radiate_surf_mat="Steel-oxidised-surface", + shell_conduction=False, ): """Assign a surface wall boundary condition with specified temperature. @@ -3871,34 +3846,33 @@ def assign_stationary_wall_with_temperature( shell_conduction=shell_conduction, ) - @pyaedt_function_handler() + @pyaedt_function_handler(htc_dataset="htc") def assign_stationary_wall_with_htc( - self, - geometry, - name=None, - thickness="0mm", - material="Al-Extruded", - htc="0w_per_m2kel", - ref_temperature="AmbientTemp", - ht_correlation=False, - ht_correlation_type="Natural Convection", - ht_correlation_fluid="air", - ht_correlation_flow_type="Turbulent", - ht_correlation_flow_direction="X", - ht_correlation_value_type="Average Values", - ht_correlation_free_stream_velocity="1m_per_sec", - ht_correlation_surface="Vertical", - ht_correlation_amb_temperature="AmbientTemp", - ext_surf_rad=False, - ext_surf_rad_material="Stainless-steel-cleaned", - ext_surf_rad_ref_temp="AmbientTemp", - ext_surf_rad_view_factor="1", - radiate=False, - radiate_surf_mat="Steel-oxidised-surface", - shell_conduction=False, - **kwargs + self, + geometry, + name=None, + thickness="0mm", + material="Al-Extruded", + htc="0w_per_m2kel", + ref_temperature="AmbientTemp", + ht_correlation=False, + ht_correlation_type="Natural Convection", + ht_correlation_fluid="air", + ht_correlation_flow_type="Turbulent", + ht_correlation_flow_direction="X", + ht_correlation_value_type="Average Values", + ht_correlation_free_stream_velocity="1m_per_sec", + ht_correlation_surface="Vertical", + ht_correlation_amb_temperature="AmbientTemp", + ext_surf_rad=False, + ext_surf_rad_material="Stainless-steel-cleaned", + ext_surf_rad_ref_temp="AmbientTemp", + ext_surf_rad_view_factor="1", + radiate=False, + radiate_surf_mat="Steel-oxidised-surface", + shell_conduction=False, ): - """Assign a surface wall boundary condition with specified heat transfer coefficient. + """Assign a surface wall boundary condition with a given heat transfer coefficient. Parameters ---------- @@ -3996,59 +3970,31 @@ def assign_stationary_wall_with_htc( >>> oModule.AssignStationaryWallBoundary """ - if kwargs.get("htc_dataset", None): - return self.assign_stationary_wall( - geometry, - "Heat Transfer Coefficient", - name=name, - thickness=thickness, - material=material, - htc=htc, - htc_dataset=kwargs["htc_dataset"], - ref_temperature=ref_temperature, - ht_correlation=ht_correlation, - ht_correlation_type=ht_correlation_type, - ht_correlation_fluid=ht_correlation_fluid, - ht_correlation_flow_type=ht_correlation_flow_type, - ht_correlation_flow_direction=ht_correlation_flow_direction, - ht_correlation_value_type=ht_correlation_value_type, - ht_correlation_free_stream_velocity=ht_correlation_free_stream_velocity, - ht_correlation_surface=ht_correlation_amb_temperature, - ht_correlation_amb_temperature=ht_correlation_surface, - ext_surf_rad=ext_surf_rad, - ext_surf_rad_material=ext_surf_rad_material, - ext_surf_rad_ref_temp=ext_surf_rad_ref_temp, - ext_surf_rad_view_factor=ext_surf_rad_view_factor, - radiate=radiate, - radiate_surf_mat=radiate_surf_mat, - shell_conduction=shell_conduction, - ) - else: - return self.assign_stationary_wall( - geometry, - "Heat Transfer Coefficient", - name=name, - thickness=thickness, - material=material, - htc=htc, - ref_temperature=ref_temperature, - ht_correlation=ht_correlation, - ht_correlation_type=ht_correlation_type, - ht_correlation_fluid=ht_correlation_fluid, - ht_correlation_flow_type=ht_correlation_flow_type, - ht_correlation_flow_direction=ht_correlation_flow_direction, - ht_correlation_value_type=ht_correlation_value_type, - ht_correlation_free_stream_velocity=ht_correlation_free_stream_velocity, - ht_correlation_surface=ht_correlation_amb_temperature, - ht_correlation_amb_temperature=ht_correlation_surface, - ext_surf_rad=ext_surf_rad, - ext_surf_rad_material=ext_surf_rad_material, - ext_surf_rad_ref_temp=ext_surf_rad_ref_temp, - ext_surf_rad_view_factor=ext_surf_rad_view_factor, - radiate=radiate, - radiate_surf_mat=radiate_surf_mat, - shell_conduction=shell_conduction, - ) + return self.assign_stationary_wall( + geometry, + "Heat Transfer Coefficient", + name=name, + thickness=thickness, + material=material, + htc=htc, + ref_temperature=ref_temperature, + ht_correlation=ht_correlation, + ht_correlation_type=ht_correlation_type, + ht_correlation_fluid=ht_correlation_fluid, + ht_correlation_flow_type=ht_correlation_flow_type, + ht_correlation_flow_direction=ht_correlation_flow_direction, + ht_correlation_value_type=ht_correlation_value_type, + ht_correlation_free_stream_velocity=ht_correlation_free_stream_velocity, + ht_correlation_surface=ht_correlation_amb_temperature, + ht_correlation_amb_temperature=ht_correlation_surface, + ext_surf_rad=ext_surf_rad, + ext_surf_rad_material=ext_surf_rad_material, + ext_surf_rad_ref_temp=ext_surf_rad_ref_temp, + ext_surf_rad_view_factor=ext_surf_rad_view_factor, + radiate=radiate, + radiate_surf_mat=radiate_surf_mat, + shell_conduction=shell_conduction, + ) @pyaedt_function_handler(setupname="name", setuptype="setup_type") def create_setup(self, name="MySetupAuto", setup_type=None, **kwargs): @@ -4129,14 +4075,14 @@ def _parse_variation_data(self, quantity, variation_type, variation_value, funct @pyaedt_function_handler() def assign_source( - self, - assignment, - thermal_condition, - assignment_value, - boundary_name=None, - radiate=False, - voltage_current_choice=False, - voltage_current_value=None, + self, + assignment, + thermal_condition, + assignment_value, + boundary_name=None, + radiate=False, + voltage_current_choice=False, + voltage_current_value=None, ): """Create a source power for a face. @@ -4361,7 +4307,7 @@ def create_resistor_network_from_matrix(self, sources_power, faces_ids, matrix, @pyaedt_function_handler def assign_solid_block( - self, object_name, power_assignment, boundary_name=None, htc=None, ext_temperature="AmbientTemp" + self, object_name, power_assignment, boundary_name=None, htc=None, ext_temperature="AmbientTemp" ): """ Assign block boundary for solid objects. @@ -4478,7 +4424,7 @@ def assign_solid_block( @pyaedt_function_handler def assign_hollow_block( - self, object_name, assignment_type, assignment_value, boundary_name=None, external_temperature="AmbientTemp" + self, object_name, assignment_type, assignment_value, boundary_name=None, external_temperature="AmbientTemp" ): """Assign block boundary for hollow objects. @@ -4643,18 +4589,18 @@ def get_fans_operating_point(self, export_file=None, setup_name=None, time_step= @pyaedt_function_handler() def assign_free_opening( - self, - assignment, - boundary_name=None, - temperature="AmbientTemp", - radiation_temperature="AmbientRadTemp", - flow_type="Pressure", - pressure="AmbientPressure", - no_reverse_flow=False, - velocity=["0m_per_sec", "0m_per_sec", "0m_per_sec"], - mass_flow_rate="0kg_per_s", - inflow=True, - direction_vector=None, + self, + assignment, + boundary_name=None, + temperature="AmbientTemp", + radiation_temperature="AmbientRadTemp", + flow_type="Pressure", + pressure="AmbientPressure", + no_reverse_flow=False, + velocity=["0m_per_sec", "0m_per_sec", "0m_per_sec"], + mass_flow_rate="0kg_per_s", + inflow=True, + direction_vector=None, ): """ Assign free opening boundary condition. @@ -4735,8 +4681,9 @@ def assign_free_opening( mass_flow_rate = str(mass_flow_rate) + "kg_per_s" if not isinstance(temperature, str) and not isinstance(temperature, (dict, BoundaryDictionary)): temperature = str(temperature) + "cel" - if not isinstance(radiation_temperature, str) and not isinstance(radiation_temperature, (dict, - BoundaryDictionary)): + if not isinstance(radiation_temperature, str) and not isinstance( + radiation_temperature, (dict, BoundaryDictionary) + ): radiation_temperature = str(radiation_temperature) + "cel" if not isinstance(pressure, str) and not isinstance(pressure, (dict, BoundaryDictionary)): pressure = str(pressure) + "pascal" @@ -4808,13 +4755,13 @@ def assign_free_opening( @pyaedt_function_handler() def assign_pressure_free_opening( - self, - assignment, - boundary_name=None, - temperature="AmbientTemp", - radiation_temperature="AmbientRadTemp", - pressure="AmbientPressure", - no_reverse_flow=False, + self, + assignment, + boundary_name=None, + temperature="AmbientTemp", + radiation_temperature="AmbientRadTemp", + pressure="AmbientPressure", + no_reverse_flow=False, ): """ Assign free opening boundary condition. @@ -4876,13 +4823,13 @@ def assign_pressure_free_opening( @pyaedt_function_handler() def assign_velocity_free_opening( - self, - assignment, - boundary_name=None, - temperature="AmbientTemp", - radiation_temperature="AmbientRadTemp", - pressure="AmbientPressure", - velocity=["0m_per_sec", "0m_per_sec", "0m_per_sec"], + self, + assignment, + boundary_name=None, + temperature="AmbientTemp", + radiation_temperature="AmbientRadTemp", + pressure="AmbientPressure", + velocity=["0m_per_sec", "0m_per_sec", "0m_per_sec"], ): """ Assign free opening boundary condition. @@ -4949,15 +4896,15 @@ def assign_velocity_free_opening( @pyaedt_function_handler() def assign_mass_flow_free_opening( - self, - assignment, - boundary_name=None, - temperature="AmbientTemp", - radiation_temperature="AmbientRadTemp", - pressure="AmbientPressure", - mass_flow_rate="0kg_per_s", - inflow=True, - direction_vector=None, + self, + assignment, + boundary_name=None, + temperature="AmbientTemp", + radiation_temperature="AmbientRadTemp", + pressure="AmbientPressure", + mass_flow_rate="0kg_per_s", + inflow=True, + direction_vector=None, ): """ Assign free opening boundary condition. @@ -5145,13 +5092,26 @@ def assign_adiabatic_plate(self, assignment, high_radiation_dict=None, low_radia return None @pyaedt_function_handler() - def assign_resistance(self, objects, boundary_name=None, total_power="0W", fluid="air", laminar=False, - loss_type="Device", linear_loss = ["1m_per_sec", "1m_per_sec", "1m_per_sec"], - quadratic_loss = [1, 1, 1], linear_loss_free_area_ratio = [1, 1, 1], - quadratic_loss_free_area_ratio = [1, 1, 1], power_law_constant=1, power_law_exponent=1, - loss_curves_x = [[0, 1], [0, 1]], loss_curves_y = [[0, 1], [0, 1]], - loss_curves_z = [[0, 1], [0, 1]], loss_curve_flow_unit = "m_per_sec", - loss_curve_pressure_unit = "n_per_meter_sq"): + def assign_resistance( + self, + objects, + boundary_name=None, + total_power="0W", + fluid="air", + laminar=False, + loss_type="Device", + linear_loss=["1m_per_sec", "1m_per_sec", "1m_per_sec"], + quadratic_loss=[1, 1, 1], + linear_loss_free_area_ratio=[1, 1, 1], + quadratic_loss_free_area_ratio=[1, 1, 1], + power_law_constant=1, + power_law_exponent=1, + loss_curves_x=[[0, 1], [0, 1]], + loss_curves_y=[[0, 1], [0, 1]], + loss_curves_z=[[0, 1], [0, 1]], + loss_curve_flow_unit="m_per_sec", + loss_curve_pressure_unit="n_per_meter_sq", + ): """ Assign resistance boundary condition. @@ -5241,28 +5201,38 @@ def assign_resistance(self, objects, boundary_name=None, total_power="0W", fluid Examples -------- """ - props = {"Objects": objects if isinstance(objects, list) else [objects], "Fluid Material": fluid, - "Laminar Flow": laminar} + props = { + "Objects": objects if isinstance(objects, list) else [objects], + "Fluid Material": fluid, + "Laminar Flow": laminar, + } if loss_type == "Device": - for direction, linear, quadratic, linear_far, quadratic_far in zip(["X", "Y", "Z"], linear_loss, - quadratic_loss, - linear_loss_free_area_ratio, - quadratic_loss_free_area_ratio): - props.update({ - "Linear " + direction + " Coefficient": str(linear) + "m_per_sec" if not isinstance(linear, - str) else str( - linear), - "Quadratic " + direction + " Coefficient": str(quadratic), - "Linear " + direction + " Free Area Ratio": str(linear_far), - "Quadratic " + direction + " Free Area Ratio": str(quadratic_far) - }) + for direction, linear, quadratic, linear_far, quadratic_far in zip( + ["X", "Y", "Z"], + linear_loss, + quadratic_loss, + linear_loss_free_area_ratio, + quadratic_loss_free_area_ratio, + ): + props.update( + { + "Linear " + + direction + + " Coefficient": str(linear) + "m_per_sec" if not isinstance(linear, str) else str(linear), + "Quadratic " + direction + " Coefficient": str(quadratic), + "Linear " + direction + " Free Area Ratio": str(linear_far), + "Quadratic " + direction + " Free Area Ratio": str(quadratic_far), + } + ) elif loss_type == "Power Law": - props.update({ - "Pressure Loss Model": "Power Law", - "Power Law Coefficient": power_law_constant, - "Power Law Exponent": power_law_exponent - }) + props.update( + { + "Pressure Loss Model": "Power Law", + "Power Law Coefficient": power_law_constant, + "Power Law Exponent": power_law_exponent, + } + ) elif loss_type == "Loss Curve": props.update({"Pressure Loss Model": "Loss Curve"}) for direction, values in zip(["X", "Y", "Z"], [loss_curves_x, loss_curves_y, loss_curves_z]): @@ -5270,7 +5240,7 @@ def assign_resistance(self, objects, boundary_name=None, total_power="0W", fluid props[key] = { "DimUnits": [loss_curve_flow_unit, loss_curve_pressure_unit], "X": [str(i) for i in values[0]], - "Y": [str(i) for i in values[1]] + "Y": [str(i) for i in values[1]], } if isinstance(total_power, (dict, BoundaryDictionary)): @@ -5301,8 +5271,16 @@ def assign_resistance(self, objects, boundary_name=None, total_power="0W", fluid return None @pyaedt_function_handler() - def assign_power_law_resistance(self, objects, boundary_name=None, total_power="0W", fluid="air", laminar=False, - power_law_constant=1, power_law_exponent=1): + def assign_power_law_resistance( + self, + objects, + boundary_name=None, + total_power="0W", + fluid="air", + laminar=False, + power_law_constant=1, + power_law_exponent=1, + ): """ Assign resistance boundary condition prescribing a power law. @@ -5348,16 +5326,31 @@ def assign_power_law_resistance(self, objects, boundary_name=None, total_power=" Examples -------- """ - return self.assign_resistance(objects, boundary_name=boundary_name, total_power=total_power, fluid=fluid, - laminar=laminar, loss_type="Power Law", - power_law_constant=power_law_constant, power_law_exponent=power_law_exponent) + return self.assign_resistance( + objects, + boundary_name=boundary_name, + total_power=total_power, + fluid=fluid, + laminar=laminar, + loss_type="Power Law", + power_law_constant=power_law_constant, + power_law_exponent=power_law_exponent, + ) @pyaedt_function_handler() - def assign_loss_curve_resistance(self, objects, boundary_name=None, total_power="0W", fluid="air", laminar=False, - loss_curves_x = [[0, 1], [0, 1]], - loss_curves_y = [[0, 1], [0, 1]], loss_curves_z = [[0, 1], [0, 1]], - loss_curve_flow_unit="m_per_sec", - loss_curve_pressure_unit="n_per_meter_sq"): + def assign_loss_curve_resistance( + self, + objects, + boundary_name=None, + total_power="0W", + fluid="air", + laminar=False, + loss_curves_x=[[0, 1], [0, 1]], + loss_curves_y=[[0, 1], [0, 1]], + loss_curves_z=[[0, 1], [0, 1]], + loss_curve_flow_unit="m_per_sec", + loss_curve_pressure_unit="n_per_meter_sq", + ): """ Assign resistance boundary condition prescribing a loss curve. @@ -5421,16 +5414,33 @@ def assign_loss_curve_resistance(self, objects, boundary_name=None, total_power= Examples -------- """ - return self.assign_resistance(objects, boundary_name=boundary_name, total_power=total_power, fluid=fluid, - laminar=laminar, loss_type="Loss Curve", loss_curves_x=loss_curves_x, - loss_curves_y=loss_curves_y, loss_curves_z=loss_curves_z, - loss_curve_flow_unit=loss_curve_flow_unit, - loss_curve_pressure_unit=loss_curve_pressure_unit) + return self.assign_resistance( + objects, + boundary_name=boundary_name, + total_power=total_power, + fluid=fluid, + laminar=laminar, + loss_type="Loss Curve", + loss_curves_x=loss_curves_x, + loss_curves_y=loss_curves_y, + loss_curves_z=loss_curves_z, + loss_curve_flow_unit=loss_curve_flow_unit, + loss_curve_pressure_unit=loss_curve_pressure_unit, + ) @pyaedt_function_handler() - def assign_device_resistance(self, objects, boundary_name=None, total_power="0W", fluid="air", laminar=False, - linear_loss = ["1m_per_sec", "1m_per_sec", "1m_per_sec"], quadratic_loss = [1, 1, 1], - linear_loss_free_area_ratio = [1, 1, 1], quadratic_loss_free_area_ratio = [1, 1, 1]): + def assign_device_resistance( + self, + objects, + boundary_name=None, + total_power="0W", + fluid="air", + laminar=False, + linear_loss=["1m_per_sec", "1m_per_sec", "1m_per_sec"], + quadratic_loss=[1, 1, 1], + linear_loss_free_area_ratio=[1, 1, 1], + quadratic_loss_free_area_ratio=[1, 1, 1], + ): """ Assign resistance boundary condition using the device/approach model. @@ -5488,17 +5498,34 @@ def assign_device_resistance(self, objects, boundary_name=None, total_power="0W" Examples -------- """ - return self.assign_resistance(objects, boundary_name=boundary_name, total_power=total_power, fluid=fluid, - laminar=laminar, loss_type="Device", linear_loss=linear_loss, - quadratic_loss=quadratic_loss, - linear_loss_free_area_ratio = linear_loss_free_area_ratio, - quadratic_loss_free_area_ratio = quadratic_loss_free_area_ratio) + return self.assign_resistance( + objects, + boundary_name=boundary_name, + total_power=total_power, + fluid=fluid, + laminar=laminar, + loss_type="Device", + linear_loss=linear_loss, + quadratic_loss=quadratic_loss, + linear_loss_free_area_ratio=linear_loss_free_area_ratio, + quadratic_loss_free_area_ratio=quadratic_loss_free_area_ratio, + ) @pyaedt_function_handler() - def assign_recirculation_opening(self, face_list, extract_face, thermal_specification="Temperature", - assignment_value="0cel", conductance_external_temperature=None, - flow_specification="Mass Flow", flow_assignment="0kg_per_s_m2", - flow_direction=None, start_time=None, end_time=None, boundary_name=None): + def assign_recirculation_opening( + self, + face_list, + extract_face, + thermal_specification="Temperature", + assignment_value="0cel", + conductance_external_temperature=None, + flow_specification="Mass Flow", + flow_assignment="0kg_per_s_m2", + flow_direction=None, + start_time=None, + end_time=None, + boundary_name=None, + ): """Assign recirculation faces. Parameters @@ -5569,25 +5596,23 @@ def assign_recirculation_opening(self, face_list, extract_face, thermal_specific return False if conductance_external_temperature is not None and thermal_specification != "Conductance": self.logger.warning( - '``conductance_external_temperature`` does not have any effect unless the ``thermal_specification`` ' - 'is ``"Conductance"``.') + "``conductance_external_temperature`` does not have any effect unless the ``thermal_specification`` " + 'is ``"Conductance"``.' + ) if conductance_external_temperature is not None and thermal_specification != "Conductance": self.logger.warning( - '``conductance_external_temperature`` must be specified when ``thermal_specification`` ' - 'is ``"Conductance"``. Setting ``conductance_external_temperature`` to ``"AmbientTemp"``.') + "``conductance_external_temperature`` must be specified when ``thermal_specification`` " + 'is ``"Conductance"``. Setting ``conductance_external_temperature`` to ``"AmbientTemp"``.' + ) if (start_time is not None or end_time is not None) and not self.solution_type == "Transient": - self.logger.warning( - '``start_time`` and ``end_time`` only effect steady-state simulations.') + self.logger.warning("``start_time`` and ``end_time`` only effect steady-state simulations.") elif self.solution_type == "Transient" and not (start_time is not None and end_time is not None): self.logger.warning( - '``start_time`` and ``end_time`` should be declared for transient simulations. Setting them to "0s".') + '``start_time`` and ``end_time`` should be declared for transient simulations. Setting them to "0s".' + ) start_time = "0s" end_time = "0s" - assignment_dict = { - "Conductance": "Conductance", - "Heat Input": "Heat Flow", - "Temperature": "Temperature Change" - } + assignment_dict = {"Conductance": "Conductance", "Heat Input": "Heat Flow", "Temperature": "Temperature Change"} props = {} if not isinstance(face_list[0], int): face_list = [f.id for f in face_list] @@ -5648,9 +5673,19 @@ def assign_recirculation_opening(self, face_list, extract_face, thermal_specific return _create_boundary(bound) @pyaedt_function_handler() - def assign_blower_type1(self, faces, inlet_face, fan_curve_pressure, fan_curve_flow, blower_power="0W", blade_rpm=0, - blade_angle="0rad", fan_curve_pressure_unit="n_per_meter_sq", - fan_curve_flow_unit="m3_per_s", boundary_name=None): + def assign_blower_type1( + self, + faces, + inlet_face, + fan_curve_pressure, + fan_curve_flow, + blower_power="0W", + blade_rpm=0, + blade_angle="0rad", + fan_curve_pressure_unit="n_per_meter_sq", + fan_curve_flow_unit="m3_per_s", + boundary_name=None, + ): """Assign blower type 1. Parameters @@ -5711,13 +5746,31 @@ def assign_blower_type1(self, faces, inlet_face, fan_curve_pressure, fan_curve_f props["Blade RPM"] = blade_rpm props["Fan Blade Angle"] = blade_angle props["Blower Type"] = "Type 1" - return self._assign_blower(props, faces, inlet_face, fan_curve_flow_unit, fan_curve_pressure_unit, - fan_curve_flow, fan_curve_pressure, blower_power, boundary_name) + return self._assign_blower( + props, + faces, + inlet_face, + fan_curve_flow_unit, + fan_curve_pressure_unit, + fan_curve_flow, + fan_curve_pressure, + blower_power, + boundary_name, + ) @pyaedt_function_handler() - def assign_blower_type2(self, faces, inlet_face, fan_curve_pressure, fan_curve_flow, blower_power="0W", - exhaust_angle="0rad", fan_curve_pressure_unit="n_per_meter_sq", - fan_curve_flow_unit="m3_per_s", boundary_name=None): + def assign_blower_type2( + self, + faces, + inlet_face, + fan_curve_pressure, + fan_curve_flow, + blower_power="0W", + exhaust_angle="0rad", + fan_curve_pressure_unit="n_per_meter_sq", + fan_curve_flow_unit="m3_per_s", + boundary_name=None, + ): """Assign blower type 2. Parameters @@ -5773,12 +5826,31 @@ def assign_blower_type2(self, faces, inlet_face, fan_curve_pressure, fan_curve_f props = {} props["Exhaust Exit Angle"] = exhaust_angle props["Blower Type"] = "Type 2" - return self._assign_blower(props, faces, inlet_face, fan_curve_flow_unit, fan_curve_pressure_unit, - fan_curve_flow, fan_curve_pressure, blower_power, boundary_name) + return self._assign_blower( + props, + faces, + inlet_face, + fan_curve_flow_unit, + fan_curve_pressure_unit, + fan_curve_flow, + fan_curve_pressure, + blower_power, + boundary_name, + ) @pyaedt_function_handler() - def _assign_blower(self, props, faces, inlet_face, fan_curve_flow_unit, fan_curve_pressure_unit, fan_curve_flow, - fan_curve_pressure, blower_power, boundary_name): + def _assign_blower( + self, + props, + faces, + inlet_face, + fan_curve_flow_unit, + fan_curve_pressure_unit, + fan_curve_flow, + fan_curve_pressure, + blower_power, + boundary_name, + ): if isinstance(faces[0], int): props["Faces"] = faces else: @@ -5800,11 +5872,21 @@ def _assign_blower(self, props, faces, inlet_face, fan_curve_flow_unit, fan_curv return _create_boundary(bound) @pyaedt_function_handler() - def assign_conducting_plate(self, obj_plate, boundary_name=None, total_power="0W", - thermal_specification="Thickness", thickness="1mm", solid_material="Al-Extruded", - conductance="0W_per_Cel", shell_conduction=False, thermal_resistance="0Kel_per_W", - low_side_rad_material=None, high_side_rad_material=None, - thermal_impedance="0celm2_per_W"): + def assign_conducting_plate( + self, + obj_plate, + boundary_name=None, + total_power="0W", + thermal_specification="Thickness", + thickness="1mm", + solid_material="Al-Extruded", + conductance="0W_per_Cel", + shell_conduction=False, + thermal_resistance="0Kel_per_W", + low_side_rad_material=None, + high_side_rad_material=None, + thermal_impedance="0celm2_per_W", + ): """ Assign thermal boundary conditions to a conducting plate. @@ -5881,9 +5963,9 @@ def assign_conducting_plate(self, obj_plate, boundary_name=None, total_power="0W props["Total Power"] = total_power props["Thermal Specification"] = thermal_specification for value, key, unit in zip( - [thickness, conductance, thermal_resistance, thermal_impedance], - ["Thickness", "Conductance", "Thermal Resistance", "Thermal Impedance"], - ["mm", "W_per_Cel", "Kel_per_W", "Cel_m2_per_W"] + [thickness, conductance, thermal_resistance, thermal_impedance], + ["Thickness", "Conductance", "Thermal Resistance", "Thermal Impedance"], + ["mm", "W_per_Cel", "Kel_per_W", "Cel_m2_per_W"], ): if thermal_specification == key: if not isinstance(value, str): @@ -5894,25 +5976,32 @@ def assign_conducting_plate(self, obj_plate, boundary_name=None, total_power="0W if low_side_rad_material is not None: props["LowSide"] = {"Radiate": False} else: - props["LowSide"] = {"Radiate": True, - "RadiateTo": "AllObjects", - "Surface Material": low_side_rad_material} + props["LowSide"] = {"Radiate": True, "RadiateTo": "AllObjects", "Surface Material": low_side_rad_material} if high_side_rad_material is not None: props["LowSide"] = {"Radiate": False} else: - props["HighSide"] = {"Radiate": True, - "RadiateTo - High": "AllObjects - High", - "Surface Material - High": high_side_rad_material} + props["HighSide"] = { + "Radiate": True, + "RadiateTo - High": "AllObjects - High", + "Surface Material - High": high_side_rad_material, + } props["Shell Conduction"] = shell_conduction if not boundary_name: boundary_name = generate_unique_name("Plate") bound = BoundaryObject(self, boundary_name, props, "Conducting Plate") return _create_boundary(bound) - def assign_conducting_plate_with_thickness(self, obj_plate, boundary_name=None, total_power="0W", - thickness="1mm", solid_material="Al-Extruded", - shell_conduction=False, low_side_rad_material=None, - high_side_rad_material=None): + def assign_conducting_plate_with_thickness( + self, + obj_plate, + boundary_name=None, + total_power="0W", + thickness="1mm", + solid_material="Al-Extruded", + shell_conduction=False, + low_side_rad_material=None, + high_side_rad_material=None, + ): """ Assign thermal boundary conditions with thickness specification to a conducting plate. @@ -5951,20 +6040,28 @@ def assign_conducting_plate_with_thickness(self, obj_plate, boundary_name=None, Boundary object when successful or ``None`` when failed. """ - return self.assign_conducting_plate(obj_plate, - boundary_name=boundary_name, - total_power=total_power, - thermal_specification="Thickness", - thickness=thickness, - solid_material=solid_material, - shell_conduction=shell_conduction, - low_side_rad_material=low_side_rad_material, - high_side_rad_material=high_side_rad_material) - - def assign_conducting_plate_with_resistance(self, obj_plate, boundary_name=None, total_power="0W", - thermal_resistance="0Kel_per_W", - shell_conduction=False, low_side_rad_material=None, - high_side_rad_material=None): + return self.assign_conducting_plate( + obj_plate, + boundary_name=boundary_name, + total_power=total_power, + thermal_specification="Thickness", + thickness=thickness, + solid_material=solid_material, + shell_conduction=shell_conduction, + low_side_rad_material=low_side_rad_material, + high_side_rad_material=high_side_rad_material, + ) + + def assign_conducting_plate_with_resistance( + self, + obj_plate, + boundary_name=None, + total_power="0W", + thermal_resistance="0Kel_per_W", + shell_conduction=False, + low_side_rad_material=None, + high_side_rad_material=None, + ): """ Assign thermal boundary conditions with thermal resistance specification to a conducting plate. @@ -6000,19 +6097,27 @@ def assign_conducting_plate_with_resistance(self, obj_plate, boundary_name=None, Boundary object when successful or ``None`` when failed. """ - return self.assign_conducting_plate(obj_plate, - boundary_name=boundary_name, - total_power=total_power, - thermal_specification="Thermal Resistance", - thermal_resistance=thermal_resistance, - shell_conduction=shell_conduction, - low_side_rad_material=low_side_rad_material, - high_side_rad_material=high_side_rad_material) - - def assign_conducting_plate_with_impedance(self, obj_plate, boundary_name=None, total_power="0W", - thermal_impedance="0celm2_per_W", - shell_conduction=False, low_side_rad_material=None, - high_side_rad_material=None): + return self.assign_conducting_plate( + obj_plate, + boundary_name=boundary_name, + total_power=total_power, + thermal_specification="Thermal Resistance", + thermal_resistance=thermal_resistance, + shell_conduction=shell_conduction, + low_side_rad_material=low_side_rad_material, + high_side_rad_material=high_side_rad_material, + ) + + def assign_conducting_plate_with_impedance( + self, + obj_plate, + boundary_name=None, + total_power="0W", + thermal_impedance="0celm2_per_W", + shell_conduction=False, + low_side_rad_material=None, + high_side_rad_material=None, + ): """ Assign thermal boundary conditions with thermal impedance specification to a conducting plate. @@ -6048,19 +6153,27 @@ def assign_conducting_plate_with_impedance(self, obj_plate, boundary_name=None, Boundary object when successful or ``None`` when failed. """ - return self.assign_conducting_plate(obj_plate, - boundary_name=boundary_name, - total_power=total_power, - thermal_specification="Thermal Impedance", - thermal_impedance=thermal_impedance, - shell_conduction=shell_conduction, - low_side_rad_material=low_side_rad_material, - high_side_rad_material=high_side_rad_material) - - def assign_conducting_plate_with_conductance(self, obj_plate, boundary_name=None, total_power="0W", - conductance="0W_per_Cel", - shell_conduction=False, low_side_rad_material=None, - high_side_rad_material=None): + return self.assign_conducting_plate( + obj_plate, + boundary_name=boundary_name, + total_power=total_power, + thermal_specification="Thermal Impedance", + thermal_impedance=thermal_impedance, + shell_conduction=shell_conduction, + low_side_rad_material=low_side_rad_material, + high_side_rad_material=high_side_rad_material, + ) + + def assign_conducting_plate_with_conductance( + self, + obj_plate, + boundary_name=None, + total_power="0W", + conductance="0W_per_Cel", + shell_conduction=False, + low_side_rad_material=None, + high_side_rad_material=None, + ): """ Assign thermal boundary conditions with conductance specification to a conducting plate. @@ -6096,14 +6209,16 @@ def assign_conducting_plate_with_conductance(self, obj_plate, boundary_name=None Boundary object when successful or ``None`` when failed. """ - return self.assign_conducting_plate(obj_plate, - boundary_name=boundary_name, - total_power=total_power, - thermal_specification="Conductance", - conductance=conductance, - shell_conduction=shell_conduction, - low_side_rad_material=low_side_rad_material, - high_side_rad_material=high_side_rad_material) + return self.assign_conducting_plate( + obj_plate, + boundary_name=boundary_name, + total_power=total_power, + thermal_specification="Conductance", + conductance=conductance, + shell_conduction=shell_conduction, + low_side_rad_material=low_side_rad_material, + high_side_rad_material=high_side_rad_material, + ) @pyaedt_function_handler def __create_dataset_assignment(self, type_assignment, ds_name, scale): @@ -6312,3 +6427,48 @@ def create_square_wave_transient_assignment(self, on_value, initial_time_off, on Boundary dictionary object that can be passed to boundary condition assignment functions. """ return SquareWaveDictionary(on_value, initial_time_off, on_time, off_time, off_value) + + +class IcepakDesignSettingsManipulation(DesignSettingsManipulation): + def __init__(self, app): + self.app = app + + def execute(self, k, v): + if k in ["AmbTemp", "AmbRadTemp"]: + if k == "AmbTemp" and isinstance(v, (dict, BoundaryDictionary)): + self.app.logger.error("Failed. Use `edit_design_settings` function.") + return self.app.design_settings["AmbTemp"] + # FIXME: Bug in native API. Uncomment when fixed + # if not self.solution_type == "Transient": + # self.logger.error("Transient assignment is supported only in transient designs.") + # return False + # ambtemp = getattr(self, "_parse_variation_data")( + # "AmbientTemperature", + # "Transient", + # variation_value=v["Values"], + # function=v["Function"], + # ) + # if ambtemp is not None: + # return ambtemp + # else: + # self.logger.error("Transient dictionary is not valid.") + # return False + else: + return self.app.value_with_units(v, "cel") + elif k == "AmbGaugePressure": + return self.app.value_with_units(v, "n_per_meter_sq") + elif k == "GravityVec": + if isinstance(v, (float, int)): + self.app.design_settings["GravityDir"] = ["Positive", "Negative"][v // 3] + v = "Global::{}".format(["X", "Y", "Z"][v - v // 3 * 3]) + return v + else: + if len(v.split("::")) == 1 and len(v) < 3: + if v.startswith("+") or v.startswith("-"): + self.app.design_settings["GravityDir"] = ["Positive", "Negative"][int(v.startswith("-"))] + v = v[-1] + return "Global::{}".format(v) + else: + return v + else: + return v diff --git a/pyaedt/maxwell.py b/pyaedt/maxwell.py index 2489fd112f0..57864cc07a1 100644 --- a/pyaedt/maxwell.py +++ b/pyaedt/maxwell.py @@ -6,11 +6,14 @@ import io import os import re +import time +from pyaedt import settings from pyaedt.application.Analysis3D import FieldAnalysis3D from pyaedt.application.Variables import decompose_variable_value from pyaedt.generic.constants import SOLUTIONS from pyaedt.generic.general_methods import generate_unique_name +from pyaedt.generic.general_methods import is_linux from pyaedt.generic.general_methods import open_file from pyaedt.generic.general_methods import pyaedt_function_handler from pyaedt.generic.general_methods import read_configuration_file @@ -1936,8 +1939,11 @@ def edit_external_circuit(self, netlist_file_path, schematic_design_name): """ if schematic_design_name not in self.design_list: return False - odesign = self.oproject.SetActiveDesign(schematic_design_name) + odesign = self.desktop_class.active_design(self.oproject, schematic_design_name) oeditor = odesign.SetActiveEditor("SchematicEditor") + if is_linux and settings.aedt_version == "2024.1": + time.sleep(1) + self.odesktop.CloseAllWindows() comps = oeditor.GetAllComponents() sources_array = [] sources_type_array = [] @@ -2915,7 +2921,7 @@ def xy_plane(self, value=True): @property def model_depth(self): """Model depth.""" - design_settings = self.design_settings() + design_settings = self.design_settings if "ModelDepth" in design_settings: value_str = design_settings["ModelDepth"] return value_str @@ -3015,6 +3021,17 @@ def assign_balloon(self, assignment, boundary=None): ---------- >>> oModule.AssignBalloon + + + Examples + -------- + Set balloon boundary condition in Maxwell 2D. + + >>> from pyaedt import Maxwell2d + >>> m2d = Maxwell2d() + >>> region_id = m2d.modeler.create_region() + >>> region_edges = region_id.edges + >>> m2d.assign_balloon(edge_list=region_edges) """ assignment = self.modeler.convert_to_selections(assignment, True) @@ -3054,6 +3071,17 @@ def assign_vector_potential(self, assignment, vector_value=0, boundary=None): ---------- >>> oModule.AssignVectorPotential + + + Examples + -------- + Set vector potential to zero at the boundary edges in Maxwell 2D. + + >>> from pyaedt import Maxwell2d + >>> m2d = Maxwell2d() + >>> region_id = m2d.modeler.create_region() + >>> region_edges = region_id.edges + >>> m2d.assign_vector_potential(input_edge=region_edges) """ assignment = self.modeler.convert_to_selections(assignment, True) diff --git a/pyaedt/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py deleted file mode 100644 index 2eb3b9740c6..00000000000 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ /dev/null @@ -1,250 +0,0 @@ -import argparse -import os -import shutil -import sys -import warnings -from xml.dom.minidom import parseString -import xml.etree.ElementTree as ET -from xml.etree.ElementTree import ParseError - -current_dir = os.path.dirname(os.path.realpath(__file__)) -pyaedt_path = os.path.normpath( - os.path.join( - current_dir, - "..", - ) -) -sys.path.append(os.path.normpath(os.path.join(pyaedt_path, ".."))) - -is_linux = os.name == "posix" -is_windows = not is_linux -pid = 0 - - -def main(): - args = parse_arguments() - add_pyaedt_to_aedt( - args.version, is_student_version=args.student, use_sys_lib=args.sys_lib, new_desktop_session=args.new_session - ) - - -def parse_arguments(): - parser = argparse.ArgumentParser(description="Install PyAEDT and setup PyAEDT toolkits in AEDT.") - parser.add_argument( - "--version", "-v", default="231", metavar="XY.Z", help="AEDT three-digit version (e.g. 231). Default=231" - ) - parser.add_argument( - "--student", "--student_version", action="store_true", help="Install toolkits for AEDT Student Version." - ) - parser.add_argument("--sys_lib", "--syslib", action="store_true", help="Install toolkits in SysLib.") - parser.add_argument( - "--new_session", action="store_true", help="Start a new session of AEDT after installing PyAEDT." - ) - - args = parser.parse_args() - args = process_arguments(args, parser) - return args - - -def process_arguments(args, parser): - if len(args.version) != 3: - parser.print_help() - parser.error("Version should be a three digit number (e.g. 231)") - - args.version = "20" + args.version[-3:-1] + "." + args.version[-1:] - return args - - -def add_pyaedt_to_aedt( - aedt_version, is_student_version=False, use_sys_lib=False, new_desktop_session=False, sys_dir="", pers_dir="" -): - if not (sys_dir or pers_dir): - from pyaedt import Desktop - from pyaedt.generic.general_methods import grpc_active_sessions - from pyaedt.generic.settings import settings - - sessions = grpc_active_sessions(aedt_version, is_student_version) - close_on_exit = True - if not sessions: - if not new_desktop_session: - print("Launching a new AEDT desktop session.") - new_desktop_session = True - else: - close_on_exit = False - settings.use_grpc_api = True - with Desktop( - specified_version=aedt_version, - non_graphical=new_desktop_session, - new_desktop_session=new_desktop_session, - student_version=is_student_version, - close_on_exit=close_on_exit, - ) as d: - desktop = sys.modules["__main__"].oDesktop - pers1 = os.path.join(desktop.GetPersonalLibDirectory(), "pyaedt") - pid = desktop.GetProcessID() - # Linking pyaedt in PersonalLib for IronPython compatibility. - if os.path.exists(pers1): - d.logger.info("PersonalLib already mapped.") - else: - if is_windows: - os.system('mklink /D "{}" "{}"'.format(pers1, pyaedt_path)) - else: - os.system('ln -s "{}" "{}"'.format(pyaedt_path, pers1)) - sys_dir = d.syslib - pers_dir = d.personallib - if pid and new_desktop_session: - try: - os.kill(pid, 9) - except Exception: - pass - - toolkits = ["Project"] - # Bug on Linux 23.1 and before where Project level toolkits don't show up. Thus copying to individual design - # toolkits. - if is_linux and aedt_version <= "2023.1": - toolkits = [ - "2DExtractor", - "CircuitDesign", - "HFSS", - "HFSS-IE", - "HFSS3DLayoutDesign", - "Icepak", - "Maxwell2D", - "Maxwell3D", - "Q3DExtractor", - "Mechanical", - ] - - for product in toolkits: - if use_sys_lib: - try: - sys_dir = os.path.join(sys_dir, "Toolkits") - install_toolkit(sys_dir, product, aedt_version) - print("Installed toolkit for {} in sys lib.".format(product)) - # d.logger.info("Installed toolkit for {} in sys lib.".format(product)) - - except IOError: - pers_dir = os.path.join(pers_dir, "Toolkits") - install_toolkit(pers_dir, product, aedt_version) - print("Installed toolkit for {} in sys lib.".format(product)) - # d.logger.info("Installed toolkit for {} in personal lib.".format(product)) - else: - pers_dir = os.path.join(pers_dir, "Toolkits") - install_toolkit(pers_dir, product, aedt_version) - print("Installed toolkit for {} in sys lib.".format(product)) - # d.logger.info("Installed toolkit for {} in personal lib.".format(product)) - - -def install_toolkit(toolkit_dir, product, aedt_version): - tool_dir = os.path.join(toolkit_dir, product, "PyAEDT") - lib_dir = os.path.join(tool_dir, "Lib") - toolkit_rel_lib_dir = os.path.relpath(lib_dir, tool_dir) - # Bug on Linux 23.1 and before where Project level toolkits don't show up. Thus copying to individual design - # toolkits. - if is_linux and aedt_version <= "2023.1": - toolkit_rel_lib_dir = os.path.join("Lib", "PyAEDT") - lib_dir = os.path.join(toolkit_dir, toolkit_rel_lib_dir) - toolkit_rel_lib_dir = "../../" + toolkit_rel_lib_dir - tool_dir = os.path.join(toolkit_dir, product, "PyAEDT") - os.makedirs(lib_dir, exist_ok=True) - os.makedirs(tool_dir, exist_ok=True) - files_to_copy = ["Console", "Run_PyAEDT_Script", "Jupyter"] - # Remove hard-coded version number from Python virtual environment path, and replace it with the corresponding AEDT - # version's Python virtual environment. - version_agnostic = False - if aedt_version[2:6].replace(".", "") in sys.executable: - executable_version_agnostic = sys.executable.replace(aedt_version[2:6].replace(".", ""), "%s") - version_agnostic = True - else: - executable_version_agnostic = sys.executable - jupyter_executable = executable_version_agnostic.replace("python" + exe(), "jupyter" + exe()) - ipython_executable = executable_version_agnostic.replace("python" + exe(), "ipython" + exe()) - for file_name in files_to_copy: - with open(os.path.join(current_dir, file_name + ".py_build"), "r") as build_file: - file_name_dest = file_name.replace("_", " ") + ".py" - with open(os.path.join(tool_dir, file_name_dest), "w") as out_file: - print("Building to " + os.path.join(tool_dir, file_name_dest)) - build_file_data = build_file.read() - build_file_data = ( - build_file_data.replace("##TOOLKIT_REL_LIB_DIR##", toolkit_rel_lib_dir) - .replace("##PYTHON_EXE##", executable_version_agnostic) - .replace("##IPYTHON_EXE##", ipython_executable) - .replace("##JUPYTER_EXE##", jupyter_executable) - ) - if not version_agnostic: - build_file_data = build_file_data.replace(" % version", "") - out_file.write(build_file_data) - shutil.copyfile(os.path.join(current_dir, "console_setup.py"), os.path.join(lib_dir, "console_setup.py")) - shutil.copyfile( - os.path.join(current_dir, "jupyter_template.ipynb"), - os.path.join(lib_dir, "jupyter_template.ipynb"), - ) - if aedt_version >= "2023.2": - write_tab_config(os.path.join(toolkit_dir, product), lib_dir) - - -def write_tab_config(product_toolkit_dir, pyaedt_lib_dir, force_write=False): - tab_config_file_path = os.path.join(product_toolkit_dir, "TabConfig.xml") - if not os.path.isfile(tab_config_file_path) or force_write: - root = ET.Element("TabConfig") - else: - try: - tree = ET.parse(tab_config_file_path) - except ParseError as e: - warnings.warn("Unable to parse %s\nError received = %s" % (tab_config_file_path, str(e))) - return - root = tree.getroot() - - panels = root.findall("./panel") - if panels: - panel_names = [panel.attrib["label"] for panel in panels] - if "Panel_PyAEDT" in panel_names: - # Remove previously existing PyAEDT panel and update with newer one. - panel = [panel for panel in panels if panel.attrib["label"] == "Panel_PyAEDT"][0] - root.remove(panel) - - # Write a new "Panel_PyAEDT" sub-element. - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT") - gallery = ET.SubElement(panel, "gallery", imagewidth="120", imageheight="72") - image_rel_path = os.path.relpath(pyaedt_lib_dir, product_toolkit_dir).replace("\\", "/") + "/" - if image_rel_path == "./": - image_rel_path = "" - ET.SubElement(gallery, "button", label="PyAEDT", isLarge="1", image=image_rel_path + "images/large/pyansys.png") - group = ET.SubElement(gallery, "group", label="PyAEDT Menu", image=image_rel_path + "images/gallery/PyAEDT.png") - ET.SubElement(group, "button", label="Console", script="PyAEDT/Console") - ET.SubElement(group, "button", label="Jupyter Notebook", script="PyAEDT/Jupyter") - ET.SubElement(group, "button", label="Run PyAEDT Script", script="PyAEDT/Run PyAEDT Script") - - # Backup any existing file if present - if os.path.isfile(tab_config_file_path): - shutil.copy(tab_config_file_path, tab_config_file_path + ".orig") - - write_pretty_xml(root, tab_config_file_path) - - files_to_copy = ["images/large/pyansys.png", "images/gallery/PyAEDT.png"] - for file_name in files_to_copy: - dest_file = os.path.normpath(os.path.join(pyaedt_lib_dir, file_name)) - os.makedirs(os.path.dirname(dest_file), exist_ok=True) - shutil.copy(os.path.normpath(os.path.join(current_dir, file_name)), dest_file) - - -def write_pretty_xml(root, file_path): - """Write the XML in a pretty format.""" - # If we use the commented code below, then the previously existing lines will have double lines added. We need to - # split and ignore the double lines. - # xml_str = parseString(ET.tostring(root)).toprettyxml(indent=" " * 4) - lines = [line for line in parseString(ET.tostring(root)).toprettyxml(indent=" " * 4).split("\n") if line.strip()] - xml_str = "\n".join(lines) - - with open(file_path, "w") as f: - f.write(xml_str) - - -def exe(): - if is_windows: - return ".exe" - return "" - - -if __name__ == "__main__": - main() diff --git a/pyaedt/misc/images/gallery/PyAEDT.png b/pyaedt/misc/images/gallery/PyAEDT.png deleted file mode 100644 index a51a6cd31aa..00000000000 Binary files a/pyaedt/misc/images/gallery/PyAEDT.png and /dev/null differ diff --git a/pyaedt/misc/install_extra_toolkits.py b/pyaedt/misc/install_extra_toolkits.py deleted file mode 100644 index fc89a420d04..00000000000 --- a/pyaedt/misc/install_extra_toolkits.py +++ /dev/null @@ -1,132 +0,0 @@ -import os -import shutil -import warnings -import xml.etree.ElementTree as ET -from xml.etree.ElementTree import ParseError - -from pyaedt.misc.aedtlib_personalib_install import current_dir -from pyaedt.misc.aedtlib_personalib_install import write_pretty_xml - -available_toolkits = { - "AntennaWizard": { - "pip": "git+https://github.com/ansys/pyaedt-antenna-toolkit.git", - "image": "pyansys.png", - "toolkit_script": "ansys/aedt/toolkits/antenna/run_toolkit.py", - "installation_path": "HFSS", - "package_name": "ansys.aedt.toolkits.antenna", - }, - "ChokeWizard": { - "pip": "git+https://github.com/ansys/pyaedt-choke-toolkit.git", - "image": "pyansys.png", - "toolkit_script": "ansys/aedt/toolkits/choke/choke_toolkit.py", - "installation_path": "Project", - "package_name": "ansys.aedt.toolkits.choke", - }, - "MagnetSegmentationWizard": { - "pip": "git+https://github.com/ansys/magnet-segmentation-toolkit.git", - "image": "pyansys.png", - "toolkit_script": "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py", - "installation_path": "Maxwell3d", - "package_name": "magnet-segmentation-toolkit", - }, -} - - -def write_toolkit_config(product_toolkit_dir, pyaedt_lib_dir, toolkitname, toolkit, force_write=False): - """Write a toolkit configuration file and, if needed a button in Automation menu.""" - tab_config_file_path = os.path.join(product_toolkit_dir, "TabConfig.xml") - if not os.path.isfile(tab_config_file_path) or force_write: - root = ET.Element("TabConfig") - else: - try: - tree = ET.parse(tab_config_file_path) - except ParseError as e: - warnings.warn("Unable to parse %s\nError received = %s" % (tab_config_file_path, str(e))) - return - root = tree.getroot() - - panels = root.findall("./panel") - if panels: - panel_names = [panel.attrib["label"] for panel in panels] - if "Panel_PyAEDT_Toolkits" in panel_names: - # Remove previously existing PyAEDT panel and update with newer one. - panel = [panel for panel in panels if panel.attrib["label"] == "Panel_PyAEDT_Toolkits"][0] - else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") - else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") - - # Write a new "Panel_PyAEDT_Toolkits" sub-element. - image_rel_path = os.path.relpath(pyaedt_lib_dir, product_toolkit_dir).replace("\\", "/") + "/" - if image_rel_path == "./": - image_rel_path = "" - - buttons = panel.findall("./button") - if buttons: - button_names = [button.attrib["label"] for button in buttons] - if toolkitname in button_names: - # Remove previously existing PyAEDT panel and update with newer one. - b = [button for button in buttons if button.attrib["label"] == toolkitname][0] - panel.remove(b) - if isinstance(toolkit, str) and os.path.exists(toolkit): - image_name = os.path.split(toolkit)[-1] - else: - image_name = toolkit["image"] - image_abs_path = image_rel_path + "images/large/{}".format(image_name) - ET.SubElement( - panel, - "button", - label=toolkitname, - isLarge="1", - image=image_abs_path, - script="{}/Run PyAEDT Toolkit Script".format(toolkitname), - ) - - # Backup any existing file if present - if os.path.isfile(tab_config_file_path): - shutil.copy(tab_config_file_path, tab_config_file_path + ".orig") - - write_pretty_xml(root, tab_config_file_path) - - files_to_copy = ["images/large/{}".format(image_name)] - for file_name in files_to_copy: - dest_file = os.path.normpath(os.path.join(pyaedt_lib_dir, file_name)) - os.makedirs(os.path.dirname(dest_file), exist_ok=True) - if isinstance(toolkit, str): - shutil.copy(toolkit, dest_file) - else: - shutil.copy(os.path.normpath(os.path.join(current_dir, file_name)), dest_file) - - -def remove_toolkit_config(product_toolkit_dir, toolkitname): - """Remove a toolkit configuration file and, if needed a button in Automation menu.""" - tab_config_file_path = os.path.join(product_toolkit_dir, "TabConfig.xml") - if not os.path.isfile(tab_config_file_path): - return True - try: - tree = ET.parse(tab_config_file_path) - except ParseError as e: - warnings.warn("Unable to parse %s\nError received = %s" % (tab_config_file_path, str(e))) - return - root = tree.getroot() - - panels = root.findall("./panel") - if panels: - panel_names = [panel.attrib["label"] for panel in panels] - if "Panel_PyAEDT_Toolkits" in panel_names: - # Remove previously existing PyAEDT panel and update with newer one. - panel = [panel for panel in panels if panel.attrib["label"] == "Panel_PyAEDT_Toolkits"][0] - else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") - else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") - - buttons = panel.findall("./button") - if buttons: - button_names = [button.attrib["label"] for button in buttons] - if toolkitname in button_names: - # Remove previously existing PyAEDT panel and update with newer one. - b = [button for button in buttons if button.attrib["label"] == toolkitname][0] - panel.remove(b) - - write_pretty_xml(root, tab_config_file_path) diff --git a/pyaedt/modeler/cad/Primitives.py b/pyaedt/modeler/cad/Primitives.py index c34dde58a0b..a7913b071ae 100644 --- a/pyaedt/modeler/cad/Primitives.py +++ b/pyaedt/modeler/cad/Primitives.py @@ -69,11 +69,11 @@ def _parse_objs(self): self.__parent.cleanup_solids() self.__parent.logger.info_timer("3D Modeler objects parsed.") elif self.__obj_type == "p": - self.__parent.logger.info("Parsing design points. This operation can take time") - self.__parent.logger.reset_timer() + # self.__parent.logger.info("Parsing design points. This operation can take time") + # self.__parent.logger.reset_timer() self.__parent.add_new_points() self.__parent.cleanup_points() - self.__parent.logger.info_timer("3D Modeler objects parsed.") + # self.__parent.logger.info_timer("3D Modeler objects parsed.") elif self.__obj_type == "u": self.__parent.add_new_user_defined_component() @@ -1103,13 +1103,13 @@ def get_objects_by_material(self, material=None): if material is not None: for obj in self.object_list: if obj and ("[" in obj.material_name or "(" in obj.material_name) and obj.object_type == "Solid": - material = ( + found_material = ( self._app.odesign.GetChildObject("3D Modeler") .GetChildObject(obj.name) .GetPropEvaluatedValue("Material") .lower() ) - if material.lower() == material: + if found_material == material.lower(): obj_lst.append(obj) elif obj and (obj.material_name == material or obj.material_name == material.lower()): obj_lst.append(obj) diff --git a/pyaedt/modeler/cad/Primitives2D.py b/pyaedt/modeler/cad/Primitives2D.py index 0d640ea7724..dbf284685a5 100644 --- a/pyaedt/modeler/cad/Primitives2D.py +++ b/pyaedt/modeler/cad/Primitives2D.py @@ -287,7 +287,8 @@ def create_region(self, pad_value=300, pad_type="Percentage Offset", name="Regio Padding values to apply. If a list is not provided, the same value is applied to all padding directions. If a list of floats or strings is provided, the values are - interpreted as padding for ``["+X", "-X", "+Y", "-Y", "+Z", "-Z"]``. + interpreted as padding for ``["+X", "-X", "+Y", "-Y"]`` for XY geometry mode, + and ``["+R", "+Z", "-Z"]`` for RZ geometry mode. pad_type : str, optional Padding definition. The default is ``"Percentage Offset"``. Options are ``"Absolute Offset"``, @@ -318,24 +319,26 @@ def create_region(self, pad_value=300, pad_type="Percentage Offset", name="Regio if kwarg.get("pad_percent", False): pad_percent = kwarg["pad_percent"] pad_value = pad_percent - if isinstance(pad_value, list) and len(pad_value) < 6: - pad_value = [pad_value[i // 2 + 3 * (i % 2)] for i in range(6)] + if isinstance(pad_value, list) and len(pad_value) == 4: + pad_value = [pad_value[i // 2 + 2 * (i % 2)] for i in range(4)] pad_type = ["Absolute Offset", "Percentage Offset"][int(is_percentage)] if isinstance(pad_type, bool): pad_type = ["Absolute Offset", "Percentage Offset"][int(pad_type)] - if not isinstance(pad_value, list): - pad_value = [pad_value] * 4 if self._app.design_type == "2D Extractor" or ( self._app.design_type == "Maxwell 2D" and self._app.odesign.GetGeometryMode() == "XY" ): + if not isinstance(pad_value, list): + pad_value = [pad_value] * 4 if len(pad_value) != 4: self.logger.error("Wrong padding list provided. Four values have to be provided.") return False pad_value = [pad_value[0], pad_value[2], pad_value[1], pad_value[3], 0, 0] else: - if len(pad_value) < 3: + if not isinstance(pad_value, list): + pad_value = [pad_value] * 3 + if len(pad_value) != 3: self.logger.error("Wrong padding list provided. Three values have to be provided for RZ geometry.") return False pad_value = [pad_value[0], 0, 0, 0, pad_value[1], pad_value[2]] diff --git a/pyaedt/modeler/cad/components_3d.py b/pyaedt/modeler/cad/components_3d.py index 25e3d419424..00bca1d4bf9 100644 --- a/pyaedt/modeler/cad/components_3d.py +++ b/pyaedt/modeler/cad/components_3d.py @@ -885,7 +885,7 @@ def edit_definition(self, password=""): new_project = [i for i in self._primitives._app.project_list if i not in project_list] if new_project: - project = self._primitives._app.odesktop.SetActiveProject(new_project[0]) + project = self._primitives._app.desktop_class.active_project(new_project[0]) # project = self._primitives._app.odesktop.GetActiveProject() project_name = project.GetName() project.GetDesigns()[0].GetName() diff --git a/pyaedt/modeler/circuits/PrimitivesNexxim.py b/pyaedt/modeler/circuits/PrimitivesNexxim.py index bc43400b011..e736fb3273b 100644 --- a/pyaedt/modeler/circuits/PrimitivesNexxim.py +++ b/pyaedt/modeler/circuits/PrimitivesNexxim.py @@ -2,12 +2,15 @@ import os import random import re +import time import warnings +from pyaedt import settings from pyaedt.application.Variables import decompose_variable_value from pyaedt.generic.LoadAEDTFile import load_keyword_in_aedt_file from pyaedt.generic.constants import AEDT_UNITS from pyaedt.generic.general_methods import generate_unique_name +from pyaedt.generic.general_methods import is_linux from pyaedt.generic.general_methods import open_file from pyaedt.generic.general_methods import pyaedt_function_handler from pyaedt.modeler.circuits.PrimitivesCircuit import CircuitComponents @@ -172,10 +175,16 @@ def create_subcircuit(self, location=None, angle=None, name=None, nested_subcirc parent_name = "{}:{}".format(self._app.design_name.split("/")[0], ":U" + str(random.randint(1, 10000))) self._app.odesign.InsertDesign("Circuit Design", name, "", parent_name) + if is_linux and settings.aedt_version == "2024.1": + time.sleep(1) + self._app.odesktop.CloseAllWindows() if nested_subcircuit_id: pname = "{}:{}".format(self._app.design_name.split("/")[0], nested_subcircuit_id) - odes = self._app.oproject.SetActiveDesign(pname) + odes = self._app.desktop_class.active_design(self._app.oproject, pname) oed = odes.SetActiveEditor("SchematicEditor") + if is_linux and settings.aedt_version == "2024.1": + time.sleep(1) + self._app.odesktop.CloseAllWindows() objs = oed.GetAllElements() match = [i for i in objs if name in i] o = CircuitComponent(self, tabname=self.tab_name, custom_editor=oed) diff --git a/pyaedt/modeler/modeler3d.py b/pyaedt/modeler/modeler3d.py index 590a94e0420..b2f19ef0ec0 100644 --- a/pyaedt/modeler/modeler3d.py +++ b/pyaedt/modeler/modeler3d.py @@ -877,7 +877,7 @@ def objects_in_bounding_box(self, bounding_box, check_solids=True, check_lines=T return objects @pyaedt_function_handler() - def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import_solids=True): + def import_nastran(self, file_path, import_lines=True, lines_thickness=0, **kwargs): """Import Nastran file into 3D Modeler by converting the faces to stl and reading it. The solids are translated directly to AEDT format. @@ -890,17 +890,46 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import lines_thickness : float, optional Whether to thicken lines after creation and it's default value. Every line will be parametrized with a design variable called ``xsection_linename``. - import_solids : bool, optional - Whether to import the solids or only triangles. Default is ``True``. Returns ------- List of :class:`pyaedt.modeler.Object3d.Object3d` """ - nas_to_dict = {"Points": {}, "PointsId": {}, "Triangles": {}, "Lines": {}, "Solids": {}} + + def _write_solid_stl(triangle, nas_to_dict): + try: + points = [nas_to_dict["Points"][id] for id in triangle] + except KeyError: + return + fc = GeometryOperators.get_polygon_centroid(points) + v1 = points[0] + v2 = points[1] + cv1 = GeometryOperators.v_points(fc, v1) + cv2 = GeometryOperators.v_points(fc, v2) + if cv2[0] == cv1[0] == 0.0 and cv2[1] == cv1[1] == 0.0: + n = [0, 0, 1] + elif cv2[0] == cv1[0] == 0.0 and cv2[2] == cv1[2] == 0.0: + n = [0, 1, 0] + elif cv2[1] == cv1[1] == 0.0 and cv2[2] == cv1[2] == 0.0: + n = [1, 0, 0] + else: + n = GeometryOperators.v_cross(cv1, cv2) + + normal = GeometryOperators.normalize_vector(n) + if normal: + f.write(" facet normal {} {} {}\n".format(normal[0], normal[1], normal[2])) + f.write(" outer loop\n") + f.write(" vertex {} {} {}\n".format(points[0][0], points[0][1], points[0][2])) + f.write(" vertex {} {} {}\n".format(points[1][0], points[1][1], points[1][2])) + f.write(" vertex {} {} {}\n".format(points[2][0], points[2][1], points[2][2])) + f.write(" endloop\n") + f.write(" endfacet\n") + + nas_to_dict = {"Points": {}, "PointsId": {}, "Triangles": [], "Lines": {}, "Solids": {}} self.logger.reset_timer() self.logger.info("Loading file") + el_ids = [] with open_file(file_path, "r") as f: lines = f.read().splitlines() id = 0 @@ -927,22 +956,11 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import nas_to_dict["PointsId"][grid_id] = grid_id id += 1 else: - if tria_id in nas_to_dict["Triangles"]: - nas_to_dict["Triangles"][tria_id].append( - [ - int(n1), - int(n2), - int(n3), - ] - ) - else: - nas_to_dict["Triangles"][tria_id] = [ - [ - int(n1), - int(n2), - int(n3), - ] - ] + tri = [int(n1), int(n2), int(n3)] + tri.sort() + if tri not in nas_to_dict["Triangles"]: + nas_to_dict["Triangles"].append(tri) + elif line_type in ["GRID*", "CTRIA3*"]: grid_id = int(line[8:24]) if line_type == "CTRIA3*": @@ -955,7 +973,7 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import n2 = n2[0] + n2[1:].replace("-", "e-") n3 = line[72:88].strip() - if not n3 or n3 == "*": + if not n3 or n3.startswith("*"): lk += 1 n3 = lines[lk][8:24].strip() if "-" in n3[1:]: @@ -965,46 +983,60 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import nas_to_dict["PointsId"][grid_id] = id id += 1 else: - if tria_id in nas_to_dict["Triangles"]: - nas_to_dict["Triangles"][tria_id].append( - [ - int(n1), - int(n2), - int(n3), - ] - ) - else: - nas_to_dict["Triangles"][tria_id] = [ - [ - int(n1), - int(n2), - int(n3), - ] - ] + tri = [int(n1), int(n2), int(n3)] + tri.sort() + if tri not in nas_to_dict["Triangles"]: + nas_to_dict["Triangles"].append(tri) + elif line_type in ["CPENTA", "CHEXA", "CTETRA"]: - obj_id = int(line[16:24]) - n1 = int(line[24:32]) - n2 = int(line[32:40]) - n3 = int(line[40:48]) - n4 = int(line[48:56]) - obj_list = [line_type, n1, n2, n3, n4] + obj_id = line[16:24].strip() + n = [] + el_id = line[24:32].strip() + # n = [int(line[24:32])] + n.append(int(line[32:40])) + n.append(int(line[40:48])) + n.append(int(line[48:56])) if line_type == "CPENTA": - n5 = int(line[56:64]) - n6 = int(line[64:72]) - obj_list.extend([n5, n6]) + n.append(int(line[56:64])) + n.append(int(line[64:72])) if line_type == "CHEXA": - n5 = int(line[56:64]) - n6 = int(line[64:72]) + n.append(int(line[56:64])) + n.append(int(line[64:72])) lk += 1 - n7 = int(lines[lk][8:16].strip()) - n8 = int(lines[lk][16:24].strip()) + n.append(int(lines[lk][8:16].strip())) + n.append(int(lines[lk][16:24].strip())) + from itertools import combinations + + tris = [] + for k in list(combinations(n, 3)): + tri = [int(k[0]), int(k[1]), int(k[2])] + tris.append(tri) + nas_to_dict["Solids"]["{}_{}".format(el_id, obj_id)] = tris + if el_id not in el_ids: + el_ids.append(el_id) + elif line_type in ["CTETRA*"]: + obj_id = line[8:24].strip() + n = [] + el_id = line[24:40].strip() + # n.append(line[24:40].strip()) + n.append(line[40:56].strip()) + + n.append(line[56:72].strip()) + lk += 1 + n.extend([lines[lk][i : i + 16] for i in range(16, len(lines[lk]), 16)]) + + from itertools import combinations + + tris = [] + for k in list(combinations(n, 3)): + tri = [int(k[0]), int(k[1]), int(k[2])] + tris.append(tri) + + nas_to_dict["Solids"]["{}_{}".format(el_id, obj_id)] = tris + if el_id not in el_ids: + el_ids.append(el_id) - obj_list.extend([n5, n6, n7, n8]) - if obj_id in nas_to_dict["Solids"]: - nas_to_dict["Solids"][obj_id].append(obj_list) - else: - nas_to_dict["Solids"][obj_id] = [[i for i in obj_list]] elif line_type in ["CROD", "CBEAM"]: obj_id = int(line[16:24]) n1 = int(line[24:32]) @@ -1021,40 +1053,21 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import self.logger.info("Creating STL file with detected faces") f = open(os.path.join(self._app.working_directory, self._app.design_name + "_test.stl"), "w") f.write("solid PyaedtStl\n") - for triangles in nas_to_dict["Triangles"].values(): - for triangle in triangles: - try: - points = [nas_to_dict["Points"][id] for id in triangle] - except KeyError: - continue - fc = GeometryOperators.get_polygon_centroid(points) - v1 = points[0] - v2 = points[1] - cv1 = GeometryOperators.v_points(fc, v1) - cv2 = GeometryOperators.v_points(fc, v2) - if cv2[0] == cv1[0] == 0.0 and cv2[1] == cv1[1] == 0.0: - n = [0, 0, 1] - elif cv2[0] == cv1[0] == 0.0 and cv2[2] == cv1[2] == 0.0: - n = [0, 1, 0] - elif cv2[1] == cv1[1] == 0.0 and cv2[2] == cv1[2] == 0.0: - n = [1, 0, 0] - else: - n = GeometryOperators.v_cross(cv1, cv2) - - normal = GeometryOperators.normalize_vector(n) - if normal: - f.write(" facet normal {} {} {}\n".format(normal[0], normal[1], normal[2])) - f.write(" outer loop\n") - f.write(" vertex {} {} {}\n".format(points[0][0], points[0][1], points[0][2])) - f.write(" vertex {} {} {}\n".format(points[1][0], points[1][1], points[1][2])) - f.write(" vertex {} {} {}\n".format(points[2][0], points[2][1], points[2][2])) - f.write(" endloop\n") - f.write(" endfacet\n") + for triangle in nas_to_dict["Triangles"]: + _write_solid_stl(triangle, nas_to_dict) f.write("endsolid\n") + for solidid, solid_triangles in nas_to_dict["Solids"].items(): + f.write("solid Solid_{}\n".format(solidid)) + for triangle in solid_triangles: + _write_solid_stl(triangle, nas_to_dict) + f.write("endsolid\n") f.close() self.logger.info("STL file created") self.import_3d_cad(os.path.join(self._app.working_directory, self._app.design_name + "_test.stl")) + for el in el_ids: + obj_names = [i for i in self.solid_names if i.startswith("Solid_{}_".format(el))] + self.create_group(obj_names, group_name=el) self.logger.info_timer("Faces imported") if import_lines: @@ -1087,48 +1100,6 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import if not lines_thickness and out_poly: self.generate_object_history(out_poly) - if import_solids and nas_to_dict["Solids"]: - self.logger.reset_timer() - self.logger.info("Loading solids") - for solid_pid in nas_to_dict["Solids"]: - for solid in nas_to_dict["Solids"][solid_pid]: - points = [nas_to_dict["Points"][id] for id in solid[1:]] - if solid[0] == "CPENTA": - element1 = self._app.modeler.create_polyline( - points=[points[0], points[1], points[2]], cover_surface=True, close_surface=True - ) - element2 = self._app.modeler.create_polyline( - points=[points[3], points[4], points[5]], cover_surface=True, close_surface=True - ) - self._app.modeler.connect([element1.name, element2.name]) - element1.group_name = "PID_" + str(solid_pid) - elif solid[0] == "CHEXA": - element1 = self._app.modeler.create_polyline( - points=[points[0], points[1], points[2], points[3]], cover_surface=True, close_surface=True - ) - element2 = self._app.modeler.create_polyline( - points=[points[4], points[5], points[6], points[7]], cover_surface=True, close_surface=True - ) - self._app.modeler.connect([element1.name, element2.name]) - element1.group_name = "PID_" + str(solid_pid) - elif solid[0] == "CTETRA": - element1 = self._app.modeler.create_polyline( - points=[points[0], points[1], points[2]], cover_surface=True, close_surface=True - ) - element2 = self._app.modeler.create_polyline( - points=[points[0], points[1], points[3]], cover_surface=True, close_surface=True - ) - element3 = self._app.modeler.create_polyline( - points=[points[0], points[2], points[3]], cover_surface=True, close_surface=True - ) - element4 = self._app.modeler.create_polyline( - points=[points[1], points[2], points[3]], cover_surface=True, close_surface=True - ) - self._app.modeler.unite([element1.name, element2.name, element3.name, element4.name]) - element1.group_name = "PID_" + str(solid_pid) - - self.logger.info_timer("Solids loaded") - objs_after = [i for i in self.object_names] new_objects = [self[i] for i in objs_after if i not in objs_before] return new_objects diff --git a/pyaedt/modeler/modelerpcb.py b/pyaedt/modeler/modelerpcb.py index 413cdc7856f..4e17524d6c6 100644 --- a/pyaedt/modeler/modelerpcb.py +++ b/pyaedt/modeler/modelerpcb.py @@ -119,20 +119,7 @@ def edb(self): isaedtowned=True, oproject=self._app.oproject, ) - elif not inside_desktop: - if self._app.project_timestamp_changed: - if self._edb: - self._edb.close_edb() - from pyedb import Edb - self._edb = Edb( - self._edb_folder, - self._app.design_name, - True, - self._app._aedt_version, - isaedtowned=True, - oproject=self._app.oproject, - ) return self._edb @property @@ -530,7 +517,7 @@ def import_cadence_brd(self, input_file, output_dir=None, name=None): self._oimportexport.ImportExtracta( input_file, os.path.join(output_dir, name + ".aedb"), os.path.join(output_dir, name + ".xml") ) - self._app.__init__(self._app._desktop.GetActiveProject().GetName()) + self._app.__init__(self._app.desktop_class.active_project().GetName()) return True @pyaedt_function_handler() @@ -584,7 +571,7 @@ def import_ipc2581(self, input_file, output_dir=None, name=None): self._oimportexport.ImportIPC( input_file, os.path.join(output_dir, name + ".aedb"), os.path.join(output_dir, name + ".xml") ) - self._app.__init__(self._app._desktop.GetActiveProject().GetName()) + self._app.__init__(self._app.desktop_class.active_project().GetName()) return True @pyaedt_function_handler() diff --git a/pyaedt/modeler/schematic.py b/pyaedt/modeler/schematic.py index 2a54297351f..c6be0544f93 100644 --- a/pyaedt/modeler/schematic.py +++ b/pyaedt/modeler/schematic.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- import random import re +import time +from pyaedt import settings from pyaedt.generic.constants import AEDT_UNITS +from pyaedt.generic.general_methods import is_linux from pyaedt.generic.general_methods import pyaedt_function_handler from pyaedt.modeler.cad.Modeler import Modeler from pyaedt.modeler.circuits.PrimitivesEmit import EmitComponent @@ -533,7 +536,11 @@ def model_units(self): >>> oEditor.GetActiveUnits >>> oEditor.SetActiveUnits """ - return self.layouteditor.GetActiveUnits() + active_units = self.layouteditor.GetActiveUnits() + if is_linux and settings.aedt_version == "2024.1": + time.sleep(1) + self._app.odesktop.CloseAllWindows() + return active_units @property def layout(self): diff --git a/pyaedt/modules/AdvancedPostProcessing.py b/pyaedt/modules/AdvancedPostProcessing.py index a2b76fb55f3..6b5650d6074 100644 --- a/pyaedt/modules/AdvancedPostProcessing.py +++ b/pyaedt/modules/AdvancedPostProcessing.py @@ -328,6 +328,8 @@ def plot_field_from_fieldplot( ): """Export a field plot to an image file (JPG or PNG) using Python PyVista. + This method does not support streamlines plot. + .. note:: The PyVista module rebuilds the mesh and the overlap fields on the mesh. @@ -392,8 +394,6 @@ def plot_field_from_fieldplot( else: self.ofieldsreporter.UpdateQuantityFieldsPlots(plot_folder) - if self.field_plots[plot_name].field_type == "DC R/L Fields": - file_format = "fldplt" file_to_add = self.export_field_plot(plot_name, self._app.working_directory, file_format=file_format) model = self.get_model_plotter_geometries( generate_mesh=False, diff --git a/pyaedt/modules/MeshIcepak.py b/pyaedt/modules/MeshIcepak.py index 2292e8b5323..985df51ee86 100644 --- a/pyaedt/modules/MeshIcepak.py +++ b/pyaedt/modules/MeshIcepak.py @@ -274,7 +274,8 @@ def name(self): @name.setter def name(self, value): try: - self._app.modeler.objects_by_name[self._name].name = value + if self._app.modeler.objects_by_name[self._name].name != value: + self._app.modeler.objects_by_name[self._name].name = value except KeyError: if self._app.modeler.objects_by_name[value].history().command == "CreateSubRegion": self._name = value @@ -408,8 +409,15 @@ def parts(self, parts): class MeshSettings(object): - automatic_mesh_settings = {"MeshRegionResolution": 3} # min: 1, max: 5 - common_mesh_settings = { + """ + Class for managing mesh settings. + + It can be used like a dictionary. Available keys change according + to the type of settings chosen (manual or automatic). + """ + + _automatic_mesh_settings = {"MeshRegionResolution": 3} # min: 1, max: 5 + _common_mesh_settings = { "ProximitySizeFunction": True, "CurvatureSizeFunction": True, "EnableTransition": False, @@ -419,7 +427,7 @@ class MeshSettings(object): "Enforce2dot5DCutCell": False, "StairStepMeshing": False, } - manual_mesh_settings = { + _manual_mesh_settings = { "MaxElementSizeX": "0.02mm", "MaxElementSizeY": "0.02mm", "MaxElementSizeZ": "0.03mm", @@ -437,7 +445,7 @@ class MeshSettings(object): "MinGapY": "1mm", "MinGapZ": "1mm", } - aedt_20212_args = [ + _aedt_20212_args = [ "ProximitySizeFunction", "CurvatureSizeFunction", "EnableTransition", @@ -450,56 +458,98 @@ class MeshSettings(object): def __init__(self, mesh_class, app): self._app = app self._mesh_class = mesh_class - self.instance_settings = self.common_mesh_settings.copy() - self.instance_settings.update(self.manual_mesh_settings.copy()) - self.instance_settings.update(self.automatic_mesh_settings.copy()) + self._instance_settings = self._common_mesh_settings.copy() + self._instance_settings.update(self._manual_mesh_settings.copy()) + self._instance_settings.update(self._automatic_mesh_settings.copy()) if settings.aedt_version < "2021.2": - for arg in self.aedt_20212_args: - del self.instance_settings[arg] - - @pyaedt_function_handler() - def _dim_arg(self, value): - if isinstance(value, str): - return value - else: - return _dim_arg(value, getattr(self._mesh_class, "_model_units")) + for arg in self._aedt_20212_args: + del self._instance_settings[arg] - def parse_settings(self): + def parse_settings_as_args(self): """ Parse mesh region settings. Returns ------- - dict - List of strings containing all the parts that must be included in the subregion. + List + Arguments to pass to native APIs. """ out = [] - for k, v in self.instance_settings.items(): + for k, v in self._instance_settings.items(): out.append(k + ":=") if k in ["MaxElementSizeX", "MaxElementSizeY", "MaxElementSizeZ", "MinGapX", "MinGapY", "MinGapZ"]: - v = self._dim_arg(v) + v = _dim_arg(v, getattr(self._mesh_class, "_model_units")) out.append(v) return out - def _key_in_dict(self, key): + def parse_settings_as_dictionary(self): + """ + Parse mesh region settings. + + Returns + ------- + dict + Settings of the subregion. + """ + out = {} + for k in self.keys(): + v = self._instance_settings[k] + if k in ["MaxElementSizeX", "MaxElementSizeY", "MaxElementSizeZ", "MinGapX", "MinGapY", "MinGapZ"]: + v = _dim_arg(v, getattr(self._mesh_class, "_model_units")) + out[k] = v + return out + + def keys(self): + """ + Get mesh region settings keys. + + Returns + ------- + dict_keys + Available settings keys. + """ if self._mesh_class.manual_settings: - ref_dict = self.manual_mesh_settings + return set(self._manual_mesh_settings.keys()) | set(self._common_mesh_settings.keys()) else: - ref_dict = self.automatic_mesh_settings - return key in ref_dict or key in self.common_mesh_settings + return set(self._automatic_mesh_settings.keys()) | set(self._common_mesh_settings.keys()) + + def values(self): + """ + Get mesh region settings values. + + Returns + ------- + dict_values + Settings values. + """ + return self.parse_settings_as_dictionary().values() + + def items(self): + """ + Get mesh region settings items. + + Returns + ------- + dict_items + Settings items. + """ + return self.parse_settings_as_dictionary().items() + + def __repr__(self): + return repr(self.parse_settings_as_dictionary()) def __getitem__(self, key): - if key == "Level": + if key == "Level": # backward compatibility key = "MeshRegionResolution" - if self._key_in_dict(key): - return self.instance_settings[key] + if key in self.keys(): + return self._instance_settings[key] else: raise KeyError("Setting not available.") def __setitem__(self, key, value): - if key == "Level": + if key == "Level": # backward compatibility key = "MeshRegionResolution" - if self._key_in_dict(key): + if key in self.keys(): if key == "MeshRegionResolution": try: value = int(value) @@ -515,7 +565,7 @@ def __setitem__(self, key, value): value = 5 except TypeError: pass - self.instance_settings[key] = value + self._instance_settings[key] = value else: self._app.logger.error("Setting not available.") @@ -523,13 +573,13 @@ def __delitem__(self, key): self._app.logger.error("Setting cannot be removed.") def __iter__(self): - return self.instance_settings.__iter__() + return iter(self.keys()) def __len__(self): - return self.instance_settings.__len__() + return len(self.keys()) def __contains__(self, x): - return self.instance_settings.__contains__(x) + return x in self.keys() class MeshRegionCommon(object): @@ -619,7 +669,7 @@ def update(self): >>> oModule.EditGlobalMeshRegion """ args = ["NAME:Settings"] - args += self.settings.parse_settings() + args += self.settings.parse_settings_as_args() args += ["UserSpecifiedSettings:=", self.manual_settings] try: self._app.omeshmodule.EditGlobalMeshRegion(args) @@ -745,7 +795,7 @@ def update(self): >>> oModule.EditMeshRegion """ args = ["NAME:" + self.name, "Enable:=", self.enable] - args += self.settings.parse_settings() + args += self.settings.parse_settings_as_args() args += self._parse_assignment_value() args += ["UserSpecifiedSettings:=", self.manual_settings] try: @@ -783,10 +833,20 @@ def assignment(self): """ if isinstance(self._assignment, SubRegion): # try to update name - try: - parts = self._app.odesign.GetChildObject("Mesh").GetChildObject(self.name).GetPropValue("Parts") + if self.name in self._app.odesign.GetChildObject("Mesh").GetChildNames(): + parts = [] + subparts = [] + if "Parts" in self._app.odesign.GetChildObject("Mesh").GetChildObject(self.name).GetPropNames(): + parts = self._app.odesign.GetChildObject("Mesh").GetChildObject(self.name).GetPropValue("Parts") + if "Submodels" in self._app.odesign.GetChildObject("Mesh").GetChildObject(self.name).GetPropNames(): + subparts = ( + self._app.odesign.GetChildObject("Mesh").GetChildObject(self.name).GetPropValue("Submodels") + ) if not isinstance(parts, list): parts = [parts] + if not isinstance(subparts, list): + subparts = [subparts] + parts += subparts sub_regions = self._app.modeler.non_model_objects for sr in sub_regions: p1 = [] @@ -802,8 +862,6 @@ def assignment(self): p1 += p2 if "CreateSubRegion" == self._app.modeler[sr].history().command and all(p in p1 for p in parts): self._assignment.name = sr - except GrpcApiError: - pass return self._assignment elif isinstance(self._assignment, list): return self._assignment @@ -837,7 +895,7 @@ def create(self): self._app.logger.error("Cannot create a new mesh region with this Name") return False args = ["NAME:" + self.name, "Enable:=", self.enable] - args += self.settings.parse_settings() + args += self.settings.parse_settings_as_args() args += ["UserSpecifiedSettings:=", not self.manual_settings] args += self._parse_assignment_value() self._app.omeshmodule.AssignMeshRegion(args) @@ -1374,7 +1432,7 @@ def assign_mesh_region(self, assignment=None, level=5, name=None, **kwargs): added_obj = [i for i in objectlist2 if i not in all_objs or i in assignment] meshregion.Objects = added_obj meshregion.SubModels = None - meshregion.update() + meshregion.update() return meshregion else: return False diff --git a/pyaedt/modules/PostProcessor.py b/pyaedt/modules/PostProcessor.py index 762677b4aff..5e1820b49ff 100644 --- a/pyaedt/modules/PostProcessor.py +++ b/pyaedt/modules/PostProcessor.py @@ -3056,7 +3056,7 @@ def _create_fieldplot( except Exception: pass self._desktop.TileWindows(0) - self._oproject.SetActiveDesign(self._app.design_name) + self._app.desktop_class.active_design(self._oproject, self._app.design_name) char_set = string.ascii_uppercase + string.digits if not plot_name: @@ -3116,7 +3116,7 @@ def _create_fieldplot_line_traces( except Exception: pass self._desktop.TileWindows(0) - self._oproject.SetActiveDesign(self._app.design_name) + self._app.desktop_class.active_design(self._oproject, self._app.design_name) char_set = string.ascii_uppercase + string.digits if not plot_name: @@ -3333,33 +3333,15 @@ def _get_3d_layers_nets(self, layers, nets): new_layers = [] for k, v in self._app.modeler.user_defined_components.items(): if v.layout_component: - if not layers and not nets: - new_layers.extend( - [ - "{}:{}#t=fill".format(k, i) - for i in v.layout_component.edb_object.stackup.signal_layers.keys() - ] - ) - new_layers.extend( - ["{}:{}".format(k, i) for i in v.layout_component.edb_object.stackup.dielectric_layers.keys()] - ) - elif not nets: - for layer in layers: - if layer in v.layout_component.edb_object.stackup.signal_layers: - new_layers.append("{}:{}#t=fill".format(k, layer)) - elif layer in v.layout_component.edb_object.stackup.dielectric_layers: - new_layers.append("{}:{}".format(k, layer)) - elif not layers: - for v in self._app.modeler.user_defined_components.values(): - new_layers.extend( - [[i] + nets for i in v.layout_component.edb_object.stackup.signal_layers.keys()] - ) - else: - for layer in layers: - if layer in v.layout_component.edb_object.stackup.signal_layers: - new_layers.append([layer] + nets) - elif layer in v.layout_component.edb_object.stackup.dielectric_layers: - dielectrics.append("{}:{}".format(k, layer)) + if not layers: + layers = [i for i in v.layout_component.edb_object.stackup.stackup_layers.keys()] + if not nets: + nets = [""] + [i for i in v.layout_component.edb_object.nets.nets.keys()] + for layer in layers: + if layer in v.layout_component.edb_object.stackup.signal_layers: + new_layers.append([layer] + nets) + elif layer in v.layout_component.edb_object.stackup.dielectric_layers: + dielectrics.append("{}:{}".format(k, layer)) return dielectrics, new_layers @pyaedt_function_handler() @@ -3433,12 +3415,10 @@ def create_fieldplot_layers( return self._create_fieldplot(lst_faces, quantity, setup, intrinsics, "FacesList", name) else: dielectrics, new_layers = self._get_3d_layers_nets(layers, nets) - if nets and plot_on_surface: + if plot_on_surface: plot_type = "LayerNetsExtFace" - elif nets: - plot_type = "LayerNets" else: - plot_type = "ObjList" + plot_type = "LayerNets" if new_layers: plt = self._create_fieldplot( new_layers, quantity, setup, intrinsics, plot_type, name, create_plot=False @@ -5427,7 +5407,11 @@ def get_field_summary_data(self, setup=None, variation=None, intrinsics="", pand if pandas_output: if pd is None: raise ImportError("pandas package is needed.") - return pd.DataFrame.from_dict(out_dict) + df = pd.DataFrame.from_dict(out_dict) + for col in ["Min", "Max", "Mean", "Stdev", "Total"]: + if col in df.columns: + df[col] = df[col].astype(float) + return df return out_dict @pyaedt_function_handler(filename="output_file", design_variation="variations", setup_name="setup") diff --git a/pyaedt/modules/SetupTemplates.py b/pyaedt/modules/SetupTemplates.py index 3125de54ca0..b55424bb77f 100644 --- a/pyaedt/modules/SetupTemplates.py +++ b/pyaedt/modules/SetupTemplates.py @@ -317,6 +317,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): { "Enabled": True, "Flow Regime": "Laminar", + "Turbulent Model Eqn": "ZeroEquation", "Include Temperature": True, "Include Flow": True, "Include Gravity": False, @@ -380,6 +381,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): { "Enabled": True, "Flow Regime": "Laminar", + "Turbulent Model Eqn": "ZeroEquation", "Include Temperature": True, "Include Gravity": False, "Solution Initialization - X Velocity": "0m_per_sec", @@ -441,6 +443,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): { "Enabled": True, "Flow Regime": "Laminar", + "Turbulent Model Eqn": "ZeroEquation", "Include Flow": True, "Include Gravity": False, "Solution Initialization - X Velocity": "0m_per_sec", @@ -793,6 +796,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): { "Enabled": True, "Flow Regime": "Laminar", + "Turbulent Model Eqn": "ZeroEquation", "Include Temperature": True, "Include Flow": True, "Include Gravity": False, @@ -811,7 +815,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "Convergence Criteria - Turbulent Dissipation Rate": "0.001", "Convergence Criteria - Specific Dissipation Rate": "0.001", "Convergence Criteria - Discrete Ordinates": "1e-06", - "IsEnabled:=": False, + "IsEnabled": False, "Radiation Model": "Off", "Solar Radiation Model": "Solar Radiation Calculator", "Solar Radiation - Scattering Fraction": "0", @@ -865,9 +869,9 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "Linear Solver Stabilization - Temperature": "None", "Coupled pressure-velocity formulation": False, "Frozen Flow Simulation": False, - "Start Time:=": "0s", - "Stop Time:=": "20s", - "Time Step:=": "1s", + "Start Time": "0s", + "Stop Time": "20s", + "Time Step": "1s", "Iterations per Time Step": 20, "Import Start Time": False, "Copy Fields From Source": False, @@ -883,6 +887,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): { "Enabled": True, "Flow Regime": "Laminar", + "Turbulent Model Eqn": "ZeroEquation", "Include Temperature": True, "Include Flow": False, "Include Gravity": False, @@ -901,7 +906,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "Convergence Criteria - Turbulent Dissipation Rate": "0.001", "Convergence Criteria - Specific Dissipation Rate": "0.001", "Convergence Criteria - Discrete Ordinates": "1e-06", - "IsEnabled:=": False, + "IsEnabled": False, "Radiation Model": "Off", "Solar Radiation Model": "Solar Radiation Calculator", "Solar Radiation - Scattering Fraction": "0", @@ -955,9 +960,9 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "Linear Solver Stabilization - Temperature": "None", "Coupled pressure-velocity formulation": False, "Frozen Flow Simulation": False, - "Start Time:=": "0s", - "Stop Time:=": "20s", - "Time Step:=": "1s", + "Start Time": "0s", + "Stop Time": "20s", + "Time Step": "1s", "Iterations per Time Step": 20, "Import Start Time": False, "Copy Fields From Source": False, @@ -973,6 +978,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): { "Enabled": True, "Flow Regime": "Laminar", + "Turbulent Model Eqn": "ZeroEquation", "Include Temperature": False, "Include Flow": True, "Include Gravity": False, @@ -991,7 +997,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "Convergence Criteria - Turbulent Dissipation Rate": "0.001", "Convergence Criteria - Specific Dissipation Rate": "0.001", "Convergence Criteria - Discrete Ordinates": "1e-06", - "IsEnabled:=": False, + "IsEnabled": False, "Radiation Model": "Off", "Solar Radiation Model": "Solar Radiation Calculator", "Solar Radiation - Scattering Fraction": "0", @@ -1045,9 +1051,9 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "Linear Solver Stabilization - Temperature": "None", "Coupled pressure-velocity formulation": False, "Frozen Flow Simulation": False, - "Start Time:=": "0s", - "Stop Time:=": "20s", - "Time Step:=": "1s", + "Start Time": "0s", + "Stop Time": "20s", + "Time Step": "1s", "Iterations per Time Step": 20, "Import Start Time": False, "Copy Fields From Source": False, @@ -1150,7 +1156,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "InclBBoxOption": 1, "AuxBlock": OrderedDict({}), "DoAdaptive": True, - "Color": ["R:=", 0, "G:=", 0, "B:=", 0], # TODO: create something smart for color arrays: like a class + "Color": ["R", 0, "G", 0, "B", 0], # TODO: create something smart for color arrays: like a class "AdvancedSettings": HFSS3DLayout_AdvancedSettings, "CurveApproximation": HFSS3DLayout_CurveApproximation, "Q3D_DCSettings": HFSS3DLayout_Q3D_DCSettings, @@ -1225,7 +1231,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "ICModeLength": "50nm", "AuxBlock": OrderedDict({}), "DoAdaptive": True, - "Color": ["R:=", 0, "G:=", 0, "B:=", 0], # TODO: create something smart for color arrays: like a class + "Color": ["R", 0, "G", 0, "B", 0], # TODO: create something smart for color arrays: like a class "AdvancedSettings": HFSS3DLayout_AdvancedSettings, "CurveApproximation": HFSS3DLayout_CurveApproximation, "Q3D_DCSettings": HFSS3DLayout_Q3D_DCSettings, @@ -1240,8 +1246,8 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): HFSS3DLayout_SIWAdvancedSettings = OrderedDict( { "IncludeCoPlaneCoupling": True, - "IncludeInterPlaneCoupling:=": False, - "IncludeSplitPlaneCoupling:=": True, + "IncludeInterPlaneCoupling": False, + "IncludeSplitPlaneCoupling": True, "IncludeFringeCoupling": True, "IncludeTraceCoupling": True, "XtalkThreshold": "-34", @@ -1334,7 +1340,7 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq): "Properties": HFSS3DLayout_Properties, "CustomSetup": False, "SolveSetupType": "SIwave", - "Color": ["R:=", 0, "G:=", 0, "B:=", 0], + "Color": ["R", 0, "G", 0, "B", 0], "Position": 0, "SimSetupType": "kSIwave", "SimulationSettings": HFSS3DLayout_ACSimulationSettings, diff --git a/pyaedt/modules/SolveSetup.py b/pyaedt/modules/SolveSetup.py index 743a7267150..a482f804813 100644 --- a/pyaedt/modules/SolveSetup.py +++ b/pyaedt/modules/SolveSetup.py @@ -1901,8 +1901,9 @@ def export_to_hfss(self, output_file, keep_net_name=False): @pyaedt_function_handler() def _get_net_names(self, app, file_fullname): primitives_3d_pts_per_nets = self._get_primitives_points_per_net() + self.p_app.logger.info("Processing vias...") via_per_nets = self._get_via_position_per_net() - pass + self.p_app.logger.info("Vias processing completed.") layers_elevation = { lay.name: lay.lower_elevation + lay.thickness / 2 for lay in list(self.p_app.modeler.edb.stackup.signal_layers.values()) @@ -1918,49 +1919,49 @@ def _get_net_names(self, app, file_fullname): for obj in aedtapp.modeler.solid_objects if not obj.material_name in aedtapp.modeler.materials.dielectrics ] - for net, primitives in primitives_3d_pts_per_nets.items(): - obj_dict = {} - for position in primitives_3d_pts_per_nets[net]: + for net, positions in primitives_3d_pts_per_nets.items(): + object_names = [] + for position in positions: aedtapp_objs = [p for p in aedtapp.modeler.get_bodynames_from_position(position) if p in metal_object] - if aedtapp_objs: - for p in aedtapp.modeler.get_bodynames_from_position(position, None, False): - if p in metal_object: - obj_ind = aedtapp.modeler.objects[p].id - if obj_ind not in obj_dict: - obj_dict[obj_ind] = aedtapp.modeler.objects[obj_ind] + object_names.extend(aedtapp_objs) if net in via_per_nets: for via_pos in via_per_nets[net]: - for p in aedtapp.modeler.get_bodynames_from_position(via_pos, None, False): - if p in metal_object: - obj_ind = aedtapp.modeler.objects[p].id - if obj_ind not in obj_dict: - obj_dict[obj_ind] = aedtapp.modeler.objects[obj_ind] - for lay_el in list(layers_elevation.values()): - pad_pos = via_pos[:2] - pad_pos.append(lay_el) - pad_objs = aedtapp.modeler.get_bodynames_from_position(pad_pos, None, False) - for pad_obj in pad_objs: - if pad_obj in metal_object: - pad_ind = aedtapp.modeler.objects[pad_obj].id - if pad_ind not in obj_dict: - obj_dict[pad_ind] = aedtapp.modeler.objects[pad_ind] - obj_list = list(obj_dict.values()) + object_names.extend( + [ + p + for p in aedtapp.modeler.get_bodynames_from_position(via_pos, None, False) + if p in metal_object + ] + ) + + for lay_el in list(layers_elevation.values()): + pad_pos = via_pos[:2] + pad_pos.append(lay_el) + object_names.extend( + [ + p + for p in aedtapp.modeler.get_bodynames_from_position(pad_pos, None, False) + if p in metal_object + ] + ) + net = net.replace(".", "_") - if len(obj_list) == 1: - net = net.replace("-", "m") - net = net.replace("+", "p") - net_name = re.sub("[^a-zA-Z0-9 .\n]", "_", net) - obj_list[0].name = net_name - obj_list[0].color = [randrange(255), randrange(255), randrange(255)] - elif len(obj_list) > 1: - united_object = aedtapp.modeler.unite(obj_list, purge=True) + net = net.replace("-", "m") + net = net.replace("+", "p") + net_name = re.sub("[^a-zA-Z0-9 .\n]", "_", net) + self.p_app.logger.info("Renaming primitives for net {}...".format(net_name)) + object_names = list(set(object_names)) + if len(object_names) == 1: + + object_p = aedtapp.modeler[object_names[0]] + object_p.name = net_name + object_p.color = [randrange(255), randrange(255), randrange(255)] # nosec + elif len(object_names) > 1: + united_object = aedtapp.modeler.unite(object_names, purge=True) obj_ind = aedtapp.modeler.objects[united_object].id if obj_ind: - net = net.replace("-", "m") - net = net.replace("+", "p") - net_name = re.sub("[^a-zA-Z0-9 .\n]", "_", net) aedtapp.modeler.objects[obj_ind].name = net_name - aedtapp.modeler.objects[obj_ind].color = [randrange(255), randrange(255), randrange(255)] + aedtapp.modeler.objects[obj_ind].color = [randrange(255), randrange(255), randrange(255)] # nosec if aedtapp.design_type == "Q3D Extractor": aedtapp.auto_identify_nets() @@ -1973,21 +1974,61 @@ def _get_primitives_points_per_net(self): return net_primitives = edb.modeler.primitives_by_net primitive_dict = {} + layers_elevation = { + lay.name: lay.lower_elevation + lay.thickness / 2 + for lay in list(self.p_app.modeler.edb.stackup.signal_layers.values()) + } for net, primitives in net_primitives.items(): primitive_dict[net] = [] - n = 0 - while len(primitive_dict[net]) < len(net_primitives[net]): - if n > 1000: # adding 1000 as maximum value to prevent infinite loop - return - n += 20 - primitive_dict[net] = [] - for prim in primitives: - layer = edb.stackup.signal_layers[prim.layer_name] - z = layer.lower_elevation + layer.thickness / 2 - pt = self._get_point_inside_primitive(prim, n) - if pt: - pt.append(z) - primitive_dict[net].append(pt) + self.p_app.logger.info("Processing net {}...".format(net)) + for prim in primitives: + + if prim.layer_name not in layers_elevation: + 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()] + pt.append(z) + next_p = int(len(points) / 4) + pt = [points[next_p].X.ToDouble(), points[next_p].Y.ToDouble()] + pt.append(z) + primitive_dict[net].append(pt) + + elif "EdbPolygon" in str(prim): + pdata_orig = prim.polygon_data.edb_api + pdata = self.p_app.modeler.edb._edb.Geometry.PolygonData.CreateFromArcs( + pdata_orig.GetArcData(), True + ) + + pdata.Scale(0.99, pdata.GetBoundingCircleCenter()) + points = [[], []] + for point in list(pdata.Points): + points[0].append(point.X.ToDouble()) + points[1].append(point.Y.ToDouble()) + # points = prim.points() + pt = [points[0][0], points[1][0]] + pt.append(z) + primitive_dict[net].append(pt) + next_p = int(len(points[0]) / 4) + pt = [points[0][next_p], points[1][next_p]] + pt.append(z) + primitive_dict[net].append(pt) + next_p = int(len(points[0]) / 2) + pt = [points[0][next_p], points[1][next_p]] + pt.append(z) + primitive_dict[net].append(pt) + + else: + n = 0 + while n < 1000: + n += 10 + pt = self._get_point_inside_primitive(prim, n) + if pt: + pt.append(z) + primitive_dict[net].append(pt) + break + self.p_app.logger.info("Net processing completed.") return primitive_dict @pyaedt_function_handler() @@ -2014,14 +2055,6 @@ def _get_point_inside_primitive(self, primitive, n): if GeometryOperators.point_in_polygon([x, y], [primitive_x_points, primitive_y_points]) == 1: return [x, y] - @pyaedt_function_handler() - def _get_polygon_centroid(self, arcs=None): - if arcs: - k = len(arcs[0]) - x = sum(arcs[0]) / k - y = sum(arcs[1]) / k - return [x, y] - @pyaedt_function_handler() def _convert_edb_to_aedt_units(self, input_dict=None, output_unit=0.001): if input_dict: diff --git a/pyaedt/modules/solutions.py b/pyaedt/modules/solutions.py index 97114c46e92..1b443ba2ae5 100644 --- a/pyaedt/modules/solutions.py +++ b/pyaedt/modules/solutions.py @@ -2961,6 +2961,8 @@ def surfacePlotInstruction(self): self.plotsettings, "EnableGaussianSmoothing:=", False, + "SurfaceOnly:=", + True if self.surfaces or self.cutplanes else False, ] ) return out @@ -3259,7 +3261,19 @@ def change_plot_scale(self, minimum_value, maximum_value, is_log=False, is_db=Fa return True @pyaedt_function_handler() - def export_image(self, full_path=None, width=1920, height=1080, orientation="isometric", display_wireframe=True): + def export_image( + self, + full_path=None, + width=1920, + height=1080, + orientation="isometric", + display_wireframe=True, + selections=None, + show_region=True, + show_axis=True, + show_grid=True, + show_ruler=True, + ): """Export the active plot to an image file. .. note:: @@ -3277,7 +3291,22 @@ def export_image(self, full_path=None, width=1920, height=1080, orientation="iso ``top``, ``bottom``, ``right``, ``left``, ``front``, ``back``, and any custom orientation. display_wireframe : bool, optional - Set to ``True`` if the objects has to be put in wireframe mode. + Whether the objects has to be put in wireframe mode. Default is ``True``. + selections : str or List[str], optional + Objects to fit for the zoom on the exported image. + Default is None in which case all the objects in the design will be shown. + One important note is that, if the fieldplot extension is larger than the + selection extension, the fieldplot extension will be the one considered + for the zoom of the exported image. + show_region : bool, optional + Whether to include the air region in the exported image. Default is ``True``. + show_grid : bool, optional + Whether to display the background grid in the exported image. + Default is ``True``. + show_axis : bool, optional + Whether to display the axis triad in the exported image. Default is ``True``. + show_ruler : bool, optional + Whether to display the ruler in the exported image. Default is ``True``. Returns ------- @@ -3302,6 +3331,11 @@ def export_image(self, full_path=None, width=1920, height=1080, orientation="iso width=width, height=height, display_wireframe=display_wireframe, + selections=selections, + show_region=show_region, + show_axis=show_axis, + show_grid=show_grid, + show_ruler=show_ruler, ) full_path = check_and_download_file(full_path) if status: diff --git a/pyaedt/q3d.py b/pyaedt/q3d.py index dfb66ee6d19..51c9c714a0d 100644 --- a/pyaedt/q3d.py +++ b/pyaedt/q3d.py @@ -733,14 +733,22 @@ def export_matrix_data( self.logger.error("Export of matrix data was unsuccessful.") return False + @pyaedt_function_handler( + file_name="output_file", + setup_name="setup", + matrix_name="matrix", + num_cells="cells", + freq="frequency", + model_name="model", + ) def export_equivalent_circuit( self, - file_name, - setup_name=None, + output_file, + setup=None, sweep=None, variations=None, - matrix_name=None, - num_cells=2, + matrix=None, + cells=2, user_changed_settings=True, include_cap=True, include_cond=True, @@ -761,8 +769,8 @@ def export_equivalent_circuit( ind_limit=None, res_limit=None, cond_limit=None, - model_name=None, - freq=0, + model=None, + frequency=0, file_type="HSPICE", include_cpp=False, ): @@ -770,11 +778,11 @@ def export_equivalent_circuit( Parameters ---------- - file_name : str + output_file : str Full path for saving the matrix data to. Options for file extensions are CIR, SML, SP, PKG, SPC, LIB, CKT, BSP, DML, and ICM. - setup_name : str, optional + setup : str, optional Setup name. The default value is ``None``, in which case the first analysis setup is used. sweep : str, optional @@ -784,9 +792,9 @@ def export_equivalent_circuit( Design variation. The default is ``None``, in which case the current nominal variation is used. If you provide a design variation, use the format ``{Name}:{Value}``. - matrix_name : str, optional + matrix : str, optional Name of the matrix to show. The default is ``"Original"``. - num_cells : int, optional + cells : int, optional Number of cells in export. Default value is 2. user_changed_settings : bool, optional @@ -855,10 +863,10 @@ def export_equivalent_circuit( Inductance limit. Default value is 1nH if coupling_limit_type is 0. Default value is 0.01 if coupling_limit_type is 1. - model_name : str, optional + model : str, optional Model name or name of the sub circuit (Optional). If None then file_name is considered as model name. - freq : str, optional + frequency : str, optional Sweep frequency in Hz. Default value is 0. file_type : str, optional @@ -896,12 +904,10 @@ def export_equivalent_circuit( >>> aedtapp.modeler.duplicate_along_line(objid="Box1",vector=[0, "d", 0]) >>> mysetup = aedtapp.create_setup() >>> aedtapp.analyze_setup(mysetup.name) - >>> aedtapp.export_equivalent_circuit(file_name="test_export_circuit.cir", - ... setup_name=mysetup.name, - ... sweep="LastAdaptive", - ... variations=["d: 20mm"] + >>> aedtapp.export_equivalent_circuit(output_file="test_export_circuit.cir", + ... setup=mysetup.name,sweep="LastAdaptive", variations=["d: 20mm"]) """ - if os.path.splitext(file_name)[1] not in [ + if os.path.splitext(output_file)[1] not in [ ".cir", ".sml", ".sp", @@ -919,10 +925,10 @@ def export_equivalent_circuit( ) return False - if setup_name is None: - setup_name = self.active_setup - elif setup_name != self.active_setup: - self.logger.error("Setup named: %s is invalid. Provide a valid analysis setup name.", setup_name) + if setup is None: + setup = self.active_setup + elif setup != self.active_setup: + self.logger.error("Setup named: %s is invalid. Provide a valid analysis setup name.", setup) return False if sweep is None: sweep = self.design_solutions.default_adaptive @@ -931,7 +937,7 @@ def export_equivalent_circuit( if sweep.replace(" ", "") not in sweep_array: self.logger.error("Sweep is invalid. Provide a valid sweep.") return False - analysis_setup = setup_name + " : " + sweep.replace(" ", "") + analysis_setup = setup + " : " + sweep.replace(" ", "") if variations is None: if not self.available_variations.nominal_w_values_dict: @@ -962,11 +968,11 @@ def export_equivalent_circuit( variations_list.append(variation) variations = ",".join(variations_list) - if matrix_name is None: - matrix_name = "Original" + if matrix is None: + matrix = "Original" else: if self.matrices: - if not [matrix for matrix in self.matrices if matrix.name == matrix_name]: + if not [matrix_object for matrix_object in self.matrices if matrix_object.name == matrix]: self.logger.error("Matrix doesn't exist. Provide an existing matrix.") return False else: @@ -1047,9 +1053,9 @@ def export_equivalent_circuit( coupling_limit_value = "None" coupling_limits.append(coupling_limit_value) - if model_name is None: - model_name = self.project_name - elif model_name != self.project_name: + if model is None: + model = self.project_name + elif model != self.project_name: self.logger.error("Invalid project name.") return False @@ -1092,6 +1098,7 @@ def export_equivalent_circuit( self.logger.error("Invalid file type, possible solutions are Hspice, Welement, RLGC.") return False + cpp_settings = [] if include_cpp: if settings.aedt_version >= "2023.2": if not [x for x in [include_dcr, include_dcl, include_acr, include_acl, add_resistance] if x]: @@ -1106,22 +1113,19 @@ def export_equivalent_circuit( if isinstance(setting, tuple): if setting[0] == "NAME:CPPInfo": cpp_settings = setting - else: - include_cpp = False - cpp_settings = [] if self.modeler._is3d: try: self.oanalysis.ExportCircuit( analysis_setup, variations, - file_name, + output_file, [ "NAME:CircuitData", "MatrixName:=", - matrix_name, + matrix, "NumberOfCells:=", - str(num_cells), + str(cells), "UserHasChangedSettings:=", user_changed_settings, "IncludeCap:=", @@ -1145,8 +1149,8 @@ def export_equivalent_circuit( include_cpp, cpp_settings, ], - model_name, - freq, + model, + frequency, ) return True except Exception: @@ -1157,13 +1161,13 @@ def export_equivalent_circuit( self.oanalysis.ExportCircuit( analysis_setup, variations, - file_name, + output_file, [ "NAME:CircuitData", "MatrixName:=", - matrix_name, + matrix, "NumberOfCells:=", - str(num_cells), + str(cells), "UserHasChangedSettings:=", user_changed_settings, "IncludeCap:=", @@ -1182,9 +1186,9 @@ def export_equivalent_circuit( "RiseTime:=", rise_time, ], - model_name, + model, file_type, - freq, + frequency, ) return True except Exception: @@ -1614,6 +1618,9 @@ def _assign_source_or_sink(self, assignment, direction, name, net_name, terminal def assign_sink_to_objectface(self, assignment, direction=0, name=None, net_name=None): """Generate a sink on a face of an object. + .. deprecated:: 0.8.9 + This method is deprecated. Use the ``sink()`` method instead. + The face ID is selected based on the axis direction. It is the face that has the maximum or minimum in this axis direction. @@ -1638,6 +1645,11 @@ def assign_sink_to_objectface(self, assignment, direction=0, name=None, net_name >>> oModule.AssignSink """ + warnings.warn( + "This method is deprecated in 0.8.9. Use the ``sink()`` method.", + DeprecationWarning, + ) + assignment = self.modeler.convert_to_selections(assignment, True)[0] if isinstance(assignment, int): a = assignment @@ -1664,6 +1676,9 @@ def assign_sink_to_sheet( ): """Generate a sink on a sheet. + .. deprecated:: 0.8.9 + This method is deprecated. Use the ``sink()`` method instead. + Parameters ---------- assignment : @@ -1687,6 +1702,11 @@ def assign_sink_to_sheet( >>> oModule.AssignSink """ + warnings.warn( + "This method is deprecated in 0.8.9. Use the ``sink()`` method.", + DeprecationWarning, + ) + if not sink_name: sink_name = generate_unique_name("Sink") assignment = self.modeler.convert_to_selections(assignment, True)[0] diff --git a/pyaedt/workflows/__init__.py b/pyaedt/workflows/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/circuit/__init__.py b/pyaedt/workflows/circuit/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/circuit/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py new file mode 100644 index 00000000000..5f9eb6dd85e --- /dev/null +++ b/pyaedt/workflows/customize_automation_tab.py @@ -0,0 +1,601 @@ +# Copyright (C) 2023 - 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 os +import shutil +import subprocess # nosec +import sys +import xml.etree.ElementTree as ET # nosec + +import defusedxml.minidom + +defusedxml.defuse_stdlib() + +import warnings + +from defusedxml.ElementTree import ParseError +from defusedxml.minidom import parseString + +from pyaedt import is_linux +from pyaedt.generic.general_methods import read_toml +import pyaedt.workflows +import pyaedt.workflows.templates + + +def add_automation_tab( + name, + lib_dir, + icon_file=None, + product="Project", + template="Run PyAEDT Toolkit Script", + overwrite=False, + panel="Panel_PyAEDT_Toolkits", +): + """Add an automation tab in AEDT. + + Parameters + ---------- + name : str + Toolkit name. + lib_dir : str + Path to the library directory. + icon_file : str + Full path to the icon file. The default is the PyAnsys icon. + product : str, optional + Product directory to install the toolkit. + template : str, optional + Script template name to use + overwrite : bool, optional + Whether to overwrite the existing automation tab. The default is ``False``, in + which case is adding new tabs to the existing ones. + panel : str, optional + Panel name. The default is ``"Panel_PyAEDT_Toolkits"``. + + Returns + ------- + str + Automation tab path. + + """ + + product = __tab_map(product) + + tab_config_file_path = os.path.join(lib_dir, product, "TabConfig.xml") + if not os.path.isfile(tab_config_file_path) or overwrite: + root = ET.Element("TabConfig") + else: + try: + tree = ET.parse(tab_config_file_path) # nosec + except ParseError as e: + warnings.warn("Unable to parse %s\nError received = %s" % (tab_config_file_path, str(e))) + return + root = tree.getroot() + + panels = root.findall("./panel") + if panels: + panel_names = [panel_element.attrib["label"] for panel_element in panels] + if panel in panel_names: + # Remove previously existing PyAEDT panel and update with newer one. + panel_element = [panel_element for panel_element in panels if panel_element.attrib["label"] == panel][0] + else: + panel_element = ET.SubElement(root, "panel", label=panel) + else: + panel_element = ET.SubElement(root, "panel", label=panel) + + buttons = panel_element.findall("./button") + if buttons: + button_names = [button.attrib["label"] for button in buttons] + if name in button_names: + # Remove previously existing PyAEDT panel and update with newer one. + b = [button for button in buttons if button.attrib["label"] == name][0] + panel_element.remove(b) + + if not icon_file: + icon_file = os.path.join(os.path.dirname(pyaedt.workflows.__file__), "images", "large", "pyansys.png") + + file_name = os.path.basename(icon_file) + dest_dir = os.path.normpath(os.path.join(lib_dir, product, name, "images", "large")) + dest_file = os.path.normpath(os.path.join(dest_dir, file_name)) + os.makedirs(os.path.dirname(dest_dir), exist_ok=True) + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + shutil.copy(icon_file, dest_file) + + relative_image_path = os.path.relpath(dest_file, os.path.join(lib_dir, product)) + + ET.SubElement( + panel_element, + "button", + label=name, + isLarge="1", + image=relative_image_path, + script="{}/{}".format(name, template), + ) + + # Backup any existing file if present + if os.path.isfile(tab_config_file_path): + shutil.copy(tab_config_file_path, tab_config_file_path + ".orig") + + create_xml_tab(root, tab_config_file_path) + return tab_config_file_path + + +def remove_automation_tab(name, lib_dir, panel="Panel_PyAEDT_Toolkits"): + """Remove automation tab in AEDT. + + Parameters + ---------- + name : str + Toolkit name. + lib_dir : str + Path to the library directory. + panel : str, optional + Panel name. The default is ``"Panel_PyAEDT_Toolkits"``. + + Returns + ------- + float + Result of the dot product. + + """ + + tab_config_file_path = os.path.join(lib_dir, "TabConfig.xml") + if not os.path.isfile(tab_config_file_path): + return True + try: + tree = ET.parse(tab_config_file_path) # nosec + except ParseError as e: + warnings.warn("Unable to parse %s\nError received = %s" % (tab_config_file_path, str(e))) + return + root = tree.getroot() + + panels = root.findall("./panel") + if panels: + panel_names = [panel_element.attrib["label"] for panel_element in panels] + if panel in panel_names: + # Remove previously existing PyAEDT panel and update with newer one. + panel_element = [panel_element for panel_element in panels if panel.attrib["label"] == panel][0] + else: + panel_element = ET.SubElement(root, "panel", label=panel) + else: + panel_element = ET.SubElement(root, "panel", label=panel) + + buttons = panel_element.findall("./button") + if buttons: + button_names = [button.attrib["label"] for button in buttons] + if name in button_names: + # Remove previously existing PyAEDT panel and update with newer one. + b = [button for button in buttons if button.attrib["label"] == name][0] + panel_element.remove(b) + + create_xml_tab(root, tab_config_file_path) + + +def create_xml_tab(root, output_file): + """Write the XML file to create the automation tab. + + Parameters + ---------- + root : :class:xml.etree.ElementTree + Root element of the main panel. + output_file : str + Full name of the file to save the XML tab. + """ + lines = [line for line in parseString(ET.tostring(root)).toprettyxml(indent=" " * 4).split("\n") if line.strip()] + xml_str = "\n".join(lines) + + with open(output_file, "w") as f: + f.write(xml_str) + + +def remove_xml_tab(toolkit_dir, name, panel="Panel_PyAEDT_Toolkits"): + """Remove a toolkit configuration file.""" + tab_config_file_path = os.path.join(toolkit_dir, "TabConfig.xml") + if not os.path.isfile(tab_config_file_path): + return True + try: + tree = ET.parse(tab_config_file_path) # nosec + except ParseError as e: + warnings.warn("Unable to parse %s\nError received = %s" % (tab_config_file_path, str(e))) + return + root = tree.getroot() + + panels = root.findall("./panel") + if panels: + panel_names = [panel_element.attrib["label"] for panel_element in panels] + if panel in panel_names: + # Remove previously existing PyAEDT panel and update with newer one. + panel_element = [panel_element for panel_element in panels if panel_element.attrib["label"] == panel][0] + else: + panel_element = ET.SubElement(root, "panel", label=panel) + else: + panel_element = ET.SubElement(root, "panel", label=panel) + + buttons = panel_element.findall("./button") + if buttons: + button_names = [button.attrib["label"] for button in buttons] + if name in button_names: + # Remove previously existing PyAEDT panel and update with newer one. + b = [button for button in buttons if button.attrib["label"] == name][0] + panel_element.remove(b) + + create_xml_tab(root, tab_config_file_path) + + +def available_toolkits(): + product_list = [ + "Circuit", + "EMIT", + "HFSS", + "HFSS3DLayout", + "Icepak", + "Maxwell2D", + "Maxwell3D", + "Mechanical", + "Project", + "Q2D", + "Q3D", + "Simplorer", + ] + + product_toolkits = {} + for product in product_list: + toml_file = os.path.join(os.path.dirname(__file__), product.lower(), "toolkits_catalog.toml") + if os.path.isfile(toml_file): + toolkits_catalog = read_toml(toml_file) + product_toolkits[product] = toolkits_catalog + return product_toolkits + + +def add_script_to_menu( + desktop_object, + name, + script_file, + template_file="Run_PyAEDT_Toolkit_Script", + icon_file=None, + product="Project", + copy_to_personal_lib=True, + executable_interpreter=None, + panel="Panel_PyAEDT_Toolkits", +): + """Add a script to the ribbon menu. + + .. note:: + This method is available in AEDT 2023 R2 and later. PyAEDT must be installed + in AEDT to allow this method to run. For more information, see `Installation + `_. + + Parameters + ---------- + desktop_object : :class:pyaedt.desktop.Desktop + Desktop object. + name : str + Name of the toolkit to appear in AEDT. + script_file : str + Full path to the script file. The script will be moved to Personal Lib. + template_file : str + Script template name to use. The default is ``"Run_PyAEDT_Toolkit_Script"``. + icon_file : str, optional + Full path to the icon (a 30x30 pixel PNG file) to add to the UI. + The default is ``None``. + product : str, optional + Product to which the toolkit applies. The default is ``"Project"``, in which case + it applies to all designs. You can also specify a product, such as ``"HFSS"``. + copy_to_personal_lib : bool, optional + Whether to copy the script to Personal Lib or link the original script. Default is ``True``. + executable_interpreter : str, optional + Executable python path. The default is the one current interpreter. + panel : str, optional + Panel name. The default is ``"Panel_PyAEDT_Toolkits"``. + + Returns + ------- + bool + + """ + + if script_file and not os.path.exists(script_file): + desktop_object.logger.error("Script does not exists.") + return False + + toolkit_dir = os.path.join(desktop_object.personallib, "Toolkits") + aedt_version = desktop_object.aedt_version_id + tool_map = __tab_map(product) + tool_dir = os.path.join(toolkit_dir, tool_map, name) + lib_dir = os.path.join(tool_dir, "Lib") + toolkit_rel_lib_dir = os.path.relpath(lib_dir, tool_dir) + if is_linux and aedt_version <= "2023.1": + toolkit_rel_lib_dir = os.path.join("Lib", name) + lib_dir = os.path.join(toolkit_dir, toolkit_rel_lib_dir) + toolkit_rel_lib_dir = "../../" + toolkit_rel_lib_dir + os.makedirs(lib_dir, exist_ok=True) + os.makedirs(tool_dir, exist_ok=True) + dest_script_path = None + if script_file and copy_to_personal_lib: + dest_script_path = os.path.join(lib_dir, os.path.split(script_file)[-1]) + shutil.copy2(script_file, dest_script_path) + + version_agnostic = False + if aedt_version[2:6].replace(".", "") in sys.executable: + executable_version_agnostic = sys.executable.replace(aedt_version[2:6].replace(".", ""), "%s") + version_agnostic = True + else: + executable_version_agnostic = sys.executable + + if executable_interpreter: + executable_version_agnostic = executable_interpreter + + templates_dir = os.path.dirname(pyaedt.workflows.templates.__file__) + + ipython_executable = executable_version_agnostic.replace("python" + __exe(), "ipython" + __exe()) + jupyter_executable = executable_version_agnostic.replace("python" + __exe(), "jupyter" + __exe()) + + with open(os.path.join(templates_dir, template_file + ".py_build"), "r") as build_file: + file_name_dest = template_file.replace("_", " ") + with open(os.path.join(tool_dir, file_name_dest + ".py"), "w") as out_file: + build_file_data = build_file.read() + build_file_data = build_file_data.replace("##TOOLKIT_REL_LIB_DIR##", toolkit_rel_lib_dir) + build_file_data = build_file_data.replace("##IPYTHON_EXE##", ipython_executable) + build_file_data = build_file_data.replace("##PYTHON_EXE##", executable_version_agnostic) + build_file_data = build_file_data.replace("##JUPYTER_EXE##", jupyter_executable) + if dest_script_path: + build_file_data = build_file_data.replace("##PYTHON_SCRIPT##", dest_script_path) + + if not version_agnostic: + build_file_data = build_file_data.replace(" % version", "") + out_file.write(build_file_data) + + if aedt_version >= "2023.2": + add_automation_tab( + name, toolkit_dir, icon_file=icon_file, product=product, template=file_name_dest, panel=panel + ) + desktop_object.logger.info("{} installed".format(name)) + return True + + +def __tab_map(product): # pragma: no cover + """Map exceptions in AEDT applications.""" + if product.lower() == "hfss3dlayout": + return "HFSS3DLayoutDesign" + elif product.lower() == "circuit": + return "CircuitDesign" + elif product.lower() == "q2d": + return "2DExtractor" + elif product.lower() == "q3d": + return "Q3DExtractor" + elif product.lower() == "simplorer": + return "TwinBuilder" + else: + return product + + +def add_custom_toolkit(desktop_object, toolkit_name, wheel_toolkit=None, install=True): # pragma: no cover + """Add toolkit to AEDT Automation Tab. + + Parameters + ---------- + desktop_object : :class:pyaedt.desktop.Desktop + Desktop object. + toolkit_name : str + Name of toolkit to add. + wheel_toolkit : str + Wheelhouse path. + install : bool, optional + Whether to install the toolkit. + + Returns + ------- + bool + """ + toolkits = available_toolkits() + toolkit_info = None + product_name = None + for product in toolkits: + if toolkit_name in toolkits[product]: + toolkit_info = toolkits[product][toolkit_name] + product_name = product + break + if not toolkit_info: + desktop_object.logger.error("Toolkit does not exist.") + return False + + # Set Python version based on AEDT version + python_version = "3.10" if desktop_object.aedt_version_id > "2023.1" else "3.7" + + if not is_linux: + base_venv = os.path.normpath( + os.path.join( + desktop_object.install_path, + "commonfiles", + "CPython", + python_version.replace(".", "_"), + "winx64", + "Release", + "python", + "python.exe", + ) + ) + else: + base_venv = os.path.normpath( + os.path.join( + desktop_object.install_path, + "commonfiles", + "CPython", + python_version.replace(".", "_"), + "linx64", + "Release", + "python", + "runpython", + ) + ) + + def run_command(command): + try: + if is_linux: # pragma: no cover + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec + else: + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec + _, stderr = process.communicate() + ret_code = process.returncode + if ret_code != 0: + print("Error occurred:", stderr.decode("utf-8")) + return ret_code + except Exception as e: + print("Exception occurred:", str(e)) + return 1 # Return non-zero exit code for indicating an error + + version = desktop_object.odesktop.GetVersion()[2:6].replace(".", "") + + if not is_linux: + venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "toolkits_v{}".format(version)) + python_exe = os.path.join(venv_dir, "Scripts", "python.exe") + pip_exe = os.path.join(venv_dir, "Scripts", "pip.exe") + package_dir = os.path.join(venv_dir, "Lib") + else: + venv_dir = os.path.join(os.environ["HOME"], "pyaedt_env_ide", "toolkits_v{}".format(version)) + python_exe = os.path.join(venv_dir, "bin", "python") + pip_exe = os.path.join(venv_dir, "bin", "pip") + package_dir = os.path.join(venv_dir, "lib") + edt_root = os.path.normpath(desktop_object.odesktop.GetExeDir()) + os.environ["ANSYSEM_ROOT{}".format(version)] = edt_root + ld_library_path_dirs_to_add = [ + "{}/commonfiles/CPython/{}/linx64/Release/python/lib".format(edt_root, python_version.replace(".", "_")), + "{}/common/mono/Linux64/lib64".format(edt_root), + "{}".format(edt_root), + ] + if version < "232": + ld_library_path_dirs_to_add.append("{}/Delcross".format(edt_root)) + os.environ["LD_LIBRARY_PATH"] = ":".join(ld_library_path_dirs_to_add) + ":" + os.getenv("LD_LIBRARY_PATH", "") + + # Create virtual environment + + if not os.path.exists(venv_dir): + desktop_object.logger.info("Creating virtual environment") + run_command('"{}" -m venv "{}" --system-site-packages'.format(base_venv, venv_dir)) + desktop_object.logger.info("Virtual environment created.") + + is_installed = False + script_file = None + if os.path.isdir(os.path.normpath(os.path.join(package_dir, toolkit_info["script"]))): + script_file = os.path.normpath(os.path.join(package_dir, toolkit_info["script"])) + else: + for dirpath, dirnames, _ in os.walk(package_dir): + if "site-packages" in dirnames: + script_file = os.path.normpath(os.path.join(dirpath, "site-packages", toolkit_info["script"])) + break + if os.path.isfile(script_file): + is_installed = True + if wheel_toolkit: + wheel_toolkit = os.path.normpath(wheel_toolkit) + desktop_object.logger.info("Installing dependencies") + if install and wheel_toolkit and os.path.exists(wheel_toolkit): + desktop_object.logger.info("Starting offline installation") + if is_installed: + run_command('"{}" uninstall --yes {}'.format(pip_exe, toolkit_info["pip"])) + import zipfile + + unzipped_path = os.path.join( + os.path.dirname(wheel_toolkit), os.path.splitext(os.path.basename(wheel_toolkit))[0] + ) + if os.path.exists(unzipped_path): + shutil.rmtree(unzipped_path, ignore_errors=True) + with zipfile.ZipFile(wheel_toolkit, "r") as zip_ref: + zip_ref.extractall(unzipped_path) + + package_name = toolkit_info["package"] + run_command( + '"{}" install --no-cache-dir --no-index --find-links={} {}'.format(pip_exe, unzipped_path, package_name) + ) + elif install and not is_installed: + # Install the specified package + run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit_info["pip"])) + elif not install and is_installed: + # Uninstall toolkit + run_command('"{}" --default-timeout=1000 uninstall -y {}'.format(pip_exe, toolkit_info["package"])) + elif install and is_installed: + # Update toolkit + run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit_info["pip"])) + else: + desktop_object.logger.info("Incorrect input") + return + toolkit_dir = os.path.join(desktop_object.personallib, "Toolkits") + tool_dir = os.path.join(toolkit_dir, product_name, toolkit_info["name"]) + + script_image = os.path.abspath( + os.path.join(os.path.dirname(pyaedt.workflows.__file__), product_name.lower(), toolkit_info["icon"]) + ) + + if install: + if not os.path.exists(tool_dir): + # Install toolkit inside AEDT + add_script_to_menu( + desktop_object=desktop_object, + name=toolkit_info["name"], + script_file=script_file, + icon_file=script_image, + product=product_name, + template_file="Run_PyAEDT_Toolkit_Script", + copy_to_personal_lib=True, + executable_interpreter=python_exe, + ) + else: + if os.path.exists(tool_dir): + # Install toolkit inside AEDT + remove_script_from_menu( + desktop_object=desktop_object, + name=toolkit_info["name"], + product=product_name, + ) + + +def remove_script_from_menu(desktop_object, name, product="Project"): + """Remove a toolkit script from the menu. + + Parameters + ---------- + desktop_object : :class:pyaedt.desktop.Desktop + Desktop object. + name : str + Name of the toolkit to remove. + product : str, optional + Product to which the toolkit applies. The default is ``"Project"``, in which case + it applies to all designs. You can also specify a product, such as ``"HFSS"``. + + Returns + ------- + bool + """ + product = __tab_map(product) + toolkit_dir = os.path.join(desktop_object.personallib, "Toolkits") + aedt_version = desktop_object.aedt_version_id + tool_dir = os.path.join(toolkit_dir, product, name) + shutil.rmtree(tool_dir, ignore_errors=True) + if aedt_version >= "2023.2": + remove_xml_tab(os.path.join(toolkit_dir, product), name) + desktop_object.logger.info("{} toolkit removed successfully.".format(name)) + return True + + +def __exe(): + if not is_linux: + return ".exe" + return "" diff --git a/pyaedt/workflows/emit/__init__.py b/pyaedt/workflows/emit/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/emit/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/hfss/__init__.py b/pyaedt/workflows/hfss/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/hfss/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/hfss/images/large/antenna.png b/pyaedt/workflows/hfss/images/large/antenna.png new file mode 100644 index 00000000000..205f7c54aca Binary files /dev/null and b/pyaedt/workflows/hfss/images/large/antenna.png differ diff --git a/pyaedt/workflows/hfss/toolkits_catalog.toml b/pyaedt/workflows/hfss/toolkits_catalog.toml new file mode 100644 index 00000000000..8626340b3fa --- /dev/null +++ b/pyaedt/workflows/hfss/toolkits_catalog.toml @@ -0,0 +1,7 @@ +[AntennaWizard] +name = "Antenna Wizard" +script = "ansys/aedt/toolkits/antenna/run_toolkit.py" +icon = "images/large/antenna.png" +template = "Run_PyAEDT_Toolkit_Script" +pip = "git+https://github.com/ansys/pyaedt-antenna-toolkit.git" +package = "ansys.aedt.toolkits.antenna" diff --git a/pyaedt/workflows/hfss3dlayout/__init__.py b/pyaedt/workflows/hfss3dlayout/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/hfss3dlayout/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/hfss3dlayout/export_to_3D.py b/pyaedt/workflows/hfss3dlayout/export_to_3D.py new file mode 100644 index 00000000000..0ab251c87d7 --- /dev/null +++ b/pyaedt/workflows/hfss3dlayout/export_to_3D.py @@ -0,0 +1,111 @@ +import os +from tkinter import Button +from tkinter import Label +from tkinter import RAISED +from tkinter import StringVar +from tkinter import Tk +from tkinter import mainloop +from tkinter import ttk +from tkinter.ttk import Combobox + +import PIL.Image +import PIL.ImageTk + +from pyaedt import Desktop +from pyaedt import Hfss +from pyaedt import Hfss3dLayout +from pyaedt import Icepak +from pyaedt import Maxwell3d +from pyaedt import Q3d +import pyaedt.workflows.hfss3dlayout + +master = Tk() + +master.geometry("400x150") + +master.title("Export to 3D") + +# Load the logo for the main window +icon_path = os.path.join(os.path.dirname(pyaedt.workflows.__file__), "images", "large", "logo.png") +im = PIL.Image.open(icon_path) +photo = PIL.ImageTk.PhotoImage(im) + +# Set the icon for the main window +master.iconphoto(True, photo) + +# Configure style for ttk buttons +style = ttk.Style() +style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 10)) + +var = StringVar() +label = Label(master, textvariable=var, relief=RAISED) +var.set("Choose an option:") +label.pack(pady=10) +combo = Combobox(master, width=40) # Set the width of the combobox +combo["values"] = ("Export to HFSS", "Export to Q3D", "Export to Maxwell 3D", "Export to Icepak") +combo.current(0) +combo.pack(pady=10) + +combo.focus_set() +choice = "Export to HFSS" + + +def callback(): + global choice + choice = combo.get() + master.destroy() + return True + + +b = Button(master, text="Export", width=40, command=callback) +b.pack(pady=10) + +mainloop() + +suffixes = {"Export to HFSS": "HFSS", "Export to Q3D": "Q3D", "Export to Maxwell 3D": "M3D", "Export to Icepak": "IPK"} + +if "PYAEDT_SCRIPT_PORT" in os.environ and "PYAEDT_SCRIPT_VERSION" in os.environ: + port = os.environ["PYAEDT_SCRIPT_PORT"] + version = os.environ["PYAEDT_SCRIPT_VERSION"] +else: + port = 0 + version = "2024.1" + +with Desktop(new_desktop_session=False, close_on_exit=False, specified_version=version, port=port) as d: + proj = d.active_project() + des = d.active_design() + projname = proj.GetName() + if des.GetDesignType() in ["HFSS 3D Layout Design"]: + desname = des.GetName().split(";")[1] + else: + d.odesktop.AddMessage("", "", 3, "Hfss 3D Layout project is needed.") + d.release_desktop(False, False) + raise Exception("Hfss 3D Layout project is needed.") + h3d = Hfss3dLayout(projectname=projname, designname=desname) + setup = h3d.create_setup() + suffix = suffixes[choice] + + if choice == "Export to Q3D": + setup.export_to_q3d(h3d.project_file[:-5] + f"_{suffix}.aedt", keep_net_name=True) + else: + setup.export_to_hfss(h3d.project_file[:-5] + f"_{suffix}.aedt", keep_net_name=True) + h3d.delete_setup(setup.name) + if choice == "Export to Q3D": + app = Q3d(projectname=h3d.project_file[:-5] + f"_{suffix}.aedt") + else: + app = Hfss(projectname=h3d.project_file[:-5] + f"_{suffix}.aedt") + app2 = None + if choice == "Export to Maxwell 3D": + app2 = Maxwell3d(projectname=app.project_name) + elif choice == "Export to Icepak": + app2 = Icepak(projectname=app.project_name) + if app2: + app2.copy_solid_bodies_from( + app, + no_vacuum=False, + no_pec=False, + include_sheets=True, + ) + app2.delete_design(app.design_name) + app2.save_project() + d.logger.info("Project generated correctly.") diff --git a/pyaedt/workflows/hfss3dlayout/images/large/cad3d.png b/pyaedt/workflows/hfss3dlayout/images/large/cad3d.png new file mode 100644 index 00000000000..13e42309060 Binary files /dev/null and b/pyaedt/workflows/hfss3dlayout/images/large/cad3d.png differ diff --git a/pyaedt/workflows/hfss3dlayout/toolkits_catalog.toml b/pyaedt/workflows/hfss3dlayout/toolkits_catalog.toml new file mode 100644 index 00000000000..1149935ea8c --- /dev/null +++ b/pyaedt/workflows/hfss3dlayout/toolkits_catalog.toml @@ -0,0 +1,6 @@ +[Export3D] +name = "Export to 3D" +script = "export_to_3D.py" +icon = "images/large/cad3d.png" +template = "Run_PyAEDT_Script" +pip = "" diff --git a/pyaedt/workflows/icepak/__init__.py b/pyaedt/workflows/icepak/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/icepak/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/images/large/logo.png b/pyaedt/workflows/images/large/logo.png new file mode 100644 index 00000000000..554dc38242d Binary files /dev/null and b/pyaedt/workflows/images/large/logo.png differ diff --git a/pyaedt/misc/images/large/pyansys.png b/pyaedt/workflows/images/large/pyansys.png similarity index 100% rename from pyaedt/misc/images/large/pyansys.png rename to pyaedt/workflows/images/large/pyansys.png diff --git a/pyaedt/workflows/installer/__init__.py b/pyaedt/workflows/installer/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/installer/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/misc/console_setup.py b/pyaedt/workflows/installer/console_setup.py similarity index 97% rename from pyaedt/misc/console_setup.py rename to pyaedt/workflows/installer/console_setup.py index d044e63a1a5..b32f3273fa1 100644 --- a/pyaedt/misc/console_setup.py +++ b/pyaedt/workflows/installer/console_setup.py @@ -20,7 +20,7 @@ # to PyAEDT is created in the personal library. console_setup_dir = os.path.dirname(__file__) if "PersonalLib" in console_setup_dir: - sys.path.append(os.path.join(console_setup_dir, "..", "..", "..")) + sys.path.append(os.path.join(console_setup_dir, "../..", "..", "..")) import pyaedt diff --git a/pyaedt/workflows/installer/create_report.py b/pyaedt/workflows/installer/create_report.py new file mode 100644 index 00000000000..55a0ad348cd --- /dev/null +++ b/pyaedt/workflows/installer/create_report.py @@ -0,0 +1,39 @@ +# Generate pdf report +# ~~~~~~~~~~~~~~~~~~~ +# Generate a pdf report with output of simultion. +import os + +from pyaedt import Desktop +from pyaedt import get_pyaedt_app +from pyaedt.generic.pdf import AnsysReport + +if "PYAEDT_SCRIPT_PORT" in os.environ and "PYAEDT_SCRIPT_VERSION" in os.environ: + port = os.environ["PYAEDT_SCRIPT_PORT"] + version = os.environ["PYAEDT_SCRIPT_VERSION"] +else: + port = 0 + version = "2024.1" + +with Desktop(new_desktop_session=False, close_on_exit=False, specified_version=version, port=port) as d: + + proj = d.active_project() + des = d.active_design() + projname = proj.GetName() + desname = des.GetName() + if des.GetDesignType() in ["HFSS 3D Layout Design", "Circuit Design"]: + desname = None + app = get_pyaedt_app(projname, desname) + + report = AnsysReport(version=d.aedt_version_id, design_name=app.design_name, project_name=app.project_name) + report.create() + report.add_section() + report.add_chapter(f"{app.solution_type} Results") + report.add_sub_chapter("Plots") + report.add_text("This section contains all reports results.") + for plot in app.post.plots: + app.post.export_report_to_jpg(app.working_directory, plot.plot_name) + report.add_image(os.path.join(app.working_directory, plot.plot_name + ".jpg"), plot.plot_name) + report.add_page_break() + report.add_toc() + out = report.save_pdf(app.working_directory, "AEDT_Results.pdf") + d.odesktop.AddMessage("", "", 0, f"Report Generated. {out}") diff --git a/pyaedt/workflows/installer/images/large/console.png b/pyaedt/workflows/installer/images/large/console.png new file mode 100644 index 00000000000..5d22ff8a4c9 Binary files /dev/null and b/pyaedt/workflows/installer/images/large/console.png differ diff --git a/pyaedt/workflows/installer/images/large/jupyter.png b/pyaedt/workflows/installer/images/large/jupyter.png new file mode 100644 index 00000000000..2e3be441a11 Binary files /dev/null and b/pyaedt/workflows/installer/images/large/jupyter.png differ diff --git a/pyaedt/workflows/installer/images/large/run_script.png b/pyaedt/workflows/installer/images/large/run_script.png new file mode 100644 index 00000000000..993fe8b3f69 Binary files /dev/null and b/pyaedt/workflows/installer/images/large/run_script.png differ diff --git a/pyaedt/workflows/installer/images/large/toolkit_manager.png b/pyaedt/workflows/installer/images/large/toolkit_manager.png new file mode 100644 index 00000000000..8ee1525df77 Binary files /dev/null and b/pyaedt/workflows/installer/images/large/toolkit_manager.png differ diff --git a/pyaedt/misc/jupyter_template.ipynb b/pyaedt/workflows/installer/jupyter_template.ipynb similarity index 100% rename from pyaedt/misc/jupyter_template.ipynb rename to pyaedt/workflows/installer/jupyter_template.ipynb diff --git a/pyaedt/workflows/installer/pyaedt_installer.py b/pyaedt/workflows/installer/pyaedt_installer.py new file mode 100644 index 00000000000..eae13eeb397 --- /dev/null +++ b/pyaedt/workflows/installer/pyaedt_installer.py @@ -0,0 +1,124 @@ +# Copyright (C) 2023 - 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. + +"""Methods to add PyAEDT in AEDT.""" + +import os + +from pyaedt import is_windows +from pyaedt import pyaedt_path +from pyaedt.generic.general_methods import read_toml +from pyaedt.workflows import customize_automation_tab + + +def add_pyaedt_to_aedt( + aedt_version="2024.1", + student_version=False, + new_desktop_session=False, + non_graphical=False, +): + """Add PyAEDT tabs in AEDT. + + Parameters + ---------- + aedt_version : str, optional + AEDT release. + student_version : bool, optional + Whether to use the student version of AEDT. The default + is ``False``. + new_desktop_session : bool, optional + Whether to create a new AEDT session. The default + is ``False`` + non_graphical : bool, optional + Whether to run AEDT in non-graphical mode. The default + is ``False``. + """ + + from pyaedt import Desktop + from pyaedt.generic.general_methods import grpc_active_sessions + from pyaedt.generic.settings import settings + + sessions = grpc_active_sessions(aedt_version, student_version) + close_on_exit = True + if not sessions: + if not new_desktop_session: + print("Launching a new AEDT desktop session.") + new_desktop_session = True + else: + close_on_exit = False + settings.use_grpc_api = True + with Desktop( + specified_version=aedt_version, + non_graphical=non_graphical, + new_desktop_session=new_desktop_session, + student_version=student_version, + close_on_exit=close_on_exit, + ) as d: + personal_lib_dir = d.odesktop.GetPersonalLibDirectory() + pers1 = os.path.join(personal_lib_dir, "pyaedt") + pid = d.odesktop.GetProcessID() + # Linking pyaedt in PersonalLib for IronPython compatibility. + if os.path.exists(pers1): + d.logger.info("PersonalLib already mapped.") + else: + if is_windows: + os.system('mklink /D "{}" "{}"'.format(pers1, pyaedt_path)) + else: + os.system('ln -s "{}" "{}"'.format(pyaedt_path, pers1)) + + __add_pyaedt_tabs(d) + + if pid and new_desktop_session: + try: + os.kill(pid, 9) + except Exception: # pragma: no cover + return False + + +def __add_pyaedt_tabs(desktop_object): + """Add PyAEDT tabs in AEDT.""" + + pyaedt_tabs = ["Console", "Jupyter", "Run_Script", "ToolkitManager"] + + toolkits_catalog = read_toml(os.path.join(os.path.dirname(__file__), "toolkits_catalog.toml")) + + project_workflows_dir = os.path.dirname(__file__) + + for toolkit in pyaedt_tabs: + if toolkit in toolkits_catalog.keys(): + toolkit_info = toolkits_catalog[toolkit] + script_path = None + if toolkit_info["script"]: + script_path = os.path.join(project_workflows_dir, toolkit_info["script"]) + icon_file = os.path.join(project_workflows_dir, "images", "large", toolkit_info["icon"]) + template_name = toolkit_info["template"] + customize_automation_tab.add_script_to_menu( + desktop_object, + toolkit_info["name"], + script_path, + template_name, + icon_file=icon_file, + product="Project", + copy_to_personal_lib=True, + executable_interpreter=None, + panel="Panel_PyAEDT_Installer", + ) diff --git a/pyaedt/workflows/installer/toolkit_manager.py b/pyaedt/workflows/installer/toolkit_manager.py new file mode 100644 index 00000000000..6ec606b8228 --- /dev/null +++ b/pyaedt/workflows/installer/toolkit_manager.py @@ -0,0 +1,356 @@ +# Copyright (C) 2023 - 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 os +import tkinter as tk +from tkinter import ttk + +import PIL.Image +import PIL.ImageTk + +from pyaedt import Desktop +from pyaedt import is_windows +import pyaedt.workflows +from pyaedt.workflows.customize_automation_tab import add_custom_toolkit +from pyaedt.workflows.customize_automation_tab import add_script_to_menu +from pyaedt.workflows.customize_automation_tab import available_toolkits +from pyaedt.workflows.customize_automation_tab import remove_script_from_menu + +env_vars = ["PYAEDT_SCRIPT_VERSION", "PYAEDT_SCRIPT_PORT", "PYAEDT_STUDENT_VERSION"] +if all(var in os.environ for var in env_vars): + version = os.environ["PYAEDT_SCRIPT_VERSION"] + port = int(os.environ["PYAEDT_SCRIPT_PORT"]) + student_version = False if os.environ["PYAEDT_STUDENT_VERSION"] == "False" else True +else: + version = "241" + port = 0 + student_version = False + +if is_windows: + venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "toolkits_v{}".format(version)) + python_exe = os.path.join(venv_dir, "Scripts", "python.exe") + package_dir = os.path.join(venv_dir, "Lib", "site-packages") + +else: + venv_dir = os.path.join(os.environ["HOME"], "pyaedt_env_ide", "toolkits_v{}".format(version)) + python_exe = os.path.join(venv_dir, "bin", "python") + package_dir = os.path.join(venv_dir, "lib", "site-packages") + + +def create_toolkit_page(frame, window_name, internal_toolkits): + """Create page to display toolkit on.""" + # Available toolkits + toolkits = ["Custom"] + internal_toolkits + + max_length = max(len(item) for item in toolkits) + 1 + + # Pip or Offline radio options + installation_option_action = tk.StringVar(value="Offline") + pip_installation_radio = tk.Radiobutton(frame, text="Pip", variable=installation_option_action, value="Pip") + offline_installation_radio = tk.Radiobutton( + frame, text="Offline", variable=installation_option_action, value="Offline" + ) + pip_installation_radio.grid(row=1, column=0, padx=5, pady=5) + offline_installation_radio.grid(row=1, column=1, padx=5, pady=5) + + # Combobox with available toolkit options + toolkits_combo_label = tk.Label(frame, text="Toolkit:", width=max_length) + toolkits_combo_label.grid(row=2, column=0, padx=5, pady=5) + + toolkits_combo = ttk.Combobox( + frame, values=list(filter(lambda x: x != "", toolkits)), state="readonly", width=max_length + ) + toolkits_combo.set("Custom") + toolkits_combo.grid(row=2, column=1, padx=5, pady=5) + + # Create entry box for directory path + input_file_label = tk.Label(frame, text="Enter script path:") + input_file_label.grid(row=3, column=0, padx=5, pady=5) + input_file = tk.Entry(frame) + input_file.grid(row=3, column=1, padx=5, pady=5) + + toolkit_name_label = tk.Label(frame, text="Enter toolkit name:") + toolkit_name_label.grid(row=4, column=0, padx=5, pady=5) + toolkit_name = tk.Entry(frame) + toolkit_name.grid(row=4, column=1, padx=5, pady=5) + + # Install button + install_button = tk.Button(frame, text="Install", bg="green", fg="white", padx=20, pady=5) + install_button.grid(row=5, column=0, padx=5, pady=5, sticky="nsew") + uninstall_button = tk.Button(frame, text="Uninstall", bg="red", fg="white", padx=20, pady=5) + uninstall_button.grid(row=5, column=1, padx=5, pady=5, sticky="nsew") + + def update_page(event=None): + selected_toolkit = toolkits_combo.get() + + toolkits = available_toolkits() + selected_toolkit_info = {} + if window_name in toolkits and selected_toolkit in toolkits[window_name]: + selected_toolkit_info = toolkits[window_name][selected_toolkit] + + if selected_toolkit == "Custom" or not selected_toolkit_info.get("pip"): + install_button.config(text="Install") + uninstall_button.config(state="normal") + else: + if is_toolkit_installed(selected_toolkit, window_name): + install_button.config(text="Update") + uninstall_button.config(state="normal") + else: + install_button.config(text="Install") + uninstall_button.config(state="disabled") + + if ( + installation_option_action.get() == "Pip" + and selected_toolkit != "Custom" + and selected_toolkit_info.get("pip") + ): + toolkit_name.config(state="disabled") + input_file_label.config(text="Enter wheelhouse path:") + input_file.config(state="disabled") + elif (installation_option_action.get() == "Pip" and selected_toolkit == "Custom") or ( + installation_option_action.get() == "Offline" and selected_toolkit == "Custom" + ): + toolkit_name.config(state="normal") + input_file_label.config(text="Enter script path:") + input_file.config(state="normal") + installation_option_action.set("Offline") + elif not selected_toolkit_info.get("pip") and selected_toolkit != "Custom": + input_file.config(state="disabled") + toolkit_name.config(state="disabled") + else: + toolkit_name.config(state="disabled") + input_file_label.config(text="Enter wheelhouse path:") + input_file.config(state="normal") + + toolkits_combo.bind("<>", update_page) + + update_page() + + return install_button, uninstall_button, input_file, toolkits_combo, toolkit_name + + +def is_toolkit_installed(toolkit_name, window_name): + """Check if toolkit is installed.""" + if toolkit_name == "Custom": + return False + toolkits = available_toolkits() + script_file = os.path.normpath(os.path.join(package_dir, toolkits[window_name][toolkit_name]["script"])) + if os.path.isfile(script_file): + return True + else: + lib_dir = os.path.dirname(package_dir) + for dirpath, dirnames, _ in os.walk(lib_dir): + if "site-packages" in dirnames: + script_file = os.path.normpath( + os.path.join(dirpath, "site-packages", toolkits[window_name][toolkit_name]["script"]) + ) + if os.path.isfile(script_file): + return True + break + return False + + +def open_window(window, window_name, internal_toolkits): + """Open a window.""" + if not hasattr(window, "opened"): + window.opened = True + window.title(window_name) + install_button, uninstall_button, input_file, toolkits_combo, toolkit_name = create_toolkit_page( + window, window_name, internal_toolkits + ) + root.minsize(500, 250) + return install_button, uninstall_button, input_file, toolkits_combo, toolkit_name + else: + window.deiconify() + + +def __get_command_function( + is_install, toolkit_level, input_file, toolkits_combo, toolkit_name, install_button, uninstall_button +): + return lambda: button_is_clicked( + is_install, toolkit_level, input_file, toolkits_combo, toolkit_name, install_button, uninstall_button + ) + + +def toolkit_window(toolkit_level="Project"): + """Create interactive toolkit window.""" + toolkit_window_var = tk.Toplevel(root) + + toolkits = available_toolkits() + + if toolkit_level not in toolkits: + install_button, uninstall_button, input_file, toolkits_combo, toolkit_name = open_window( + toolkit_window_var, toolkit_level, [] + ) + else: + install_button, uninstall_button, input_file, toolkits_combo, toolkit_name = open_window( + toolkit_window_var, toolkit_level, list(toolkits[toolkit_level].keys()) + ) + toolkit_window_var.minsize(250, 150) + + install_command = __get_command_function( + True, toolkit_level, input_file, toolkits_combo, toolkit_name, install_button, uninstall_button + ) + uninstall_command = __get_command_function( + False, toolkit_level, input_file, toolkits_combo, toolkit_name, install_button, uninstall_button + ) + + install_button.configure(command=install_command) + uninstall_button.configure(command=uninstall_command) + + +def button_is_clicked( + install_action, toolkit_level, input_file, combo_toolkits, toolkit_name, install_button, uninstall_button +): + """Set up a button for installing and uninstalling the toolkit.""" + file = input_file.get() + selected_toolkit_name = combo_toolkits.get() + name = toolkit_name.get() + + desktop = Desktop( + specified_version=version, + port=port, + new_desktop_session=False, + non_graphical=False, + close_on_exit=False, + student_version=student_version, + ) + + desktop.odesktop.CloseAllWindows() + + toolkits = available_toolkits() + selected_toolkit_info = {} + icon = None + if toolkit_level in toolkits and selected_toolkit_name in toolkits[toolkit_level]: + selected_toolkit_info = toolkits[toolkit_level][selected_toolkit_name] + if not selected_toolkit_info.get("pip"): + product_path = os.path.join(os.path.dirname(pyaedt.workflows.__file__), toolkit_level.lower()) + file = os.path.abspath(os.path.join(product_path, selected_toolkit_info.get("script"))) + name = selected_toolkit_info.get("name") + icon = os.path.abspath(os.path.join(product_path, selected_toolkit_info.get("icon"))) + + if selected_toolkit_name != "Custom" and selected_toolkit_info.get("pip"): + if is_toolkit_installed(selected_toolkit_name, toolkit_level) and install_action: + desktop.logger.info("Updating {}".format(selected_toolkit_name)) + add_custom_toolkit(desktop, selected_toolkit_name, file) + install_button.config(text="Update") + uninstall_button.config(state="normal") + desktop.logger.info("{} updated".format(selected_toolkit_name)) + elif install_action: + desktop.logger.info("Installing {}".format(selected_toolkit_name)) + add_custom_toolkit(desktop, selected_toolkit_name, file) + install_button.config(text="Update") + uninstall_button.config(state="normal") + elif is_toolkit_installed(selected_toolkit_name, toolkit_level) and not install_action: + desktop.logger.info("Uninstalling {}".format(selected_toolkit_name)) + add_custom_toolkit(desktop, selected_toolkit_name, install=False) + install_button.config(text="Install") + uninstall_button.config(state="disabled") + desktop.logger.info("{} uninstalled".format(selected_toolkit_name)) + else: + desktop.logger.info("{} not installed".format(selected_toolkit_name)) + + else: + if install_action: + desktop.logger.info("Install {}".format(name)) + if is_windows: + pyaedt_venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "v{}".format(version)) + executable_interpreter = os.path.join(pyaedt_venv_dir, "Scripts", "python.exe") + else: + pyaedt_venv_dir = os.path.join(os.environ["HOME"], "pyaedt_env_ide", "v{}".format(version)) + executable_interpreter = os.path.join(pyaedt_venv_dir, "bin", "python") + + if os.path.isfile(executable_interpreter): + add_script_to_menu( + desktop_object=desktop, + name=name, + script_file=file, + product=toolkit_level, + icon_file=icon, + executable_interpreter=executable_interpreter, + ) + else: + desktop.logger.info("PyAEDT environment is not installed.") + else: + desktop.logger.info("Uninstall {}.".format(name)) + remove_script_from_menu(desktop_object=desktop, name=name, product=toolkit_level) + + desktop.odesktop.CloseAllWindows() + desktop.odesktop.RefreshToolkitUI() + desktop.release_desktop(False, False) + + +root = tk.Tk() +root.title("AEDT Toolkit Manager") + +# Load the logo for the main window +icon_path = os.path.join(os.path.dirname(pyaedt.workflows.__file__), "images", "large", "logo.png") +im = PIL.Image.open(icon_path) +photo = PIL.ImageTk.PhotoImage(im) + +# Set the icon for the main window +root.iconphoto(True, photo) + +# Configure style for ttk buttons +style = ttk.Style() +style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 10)) + +toolkit_levels = [ + "Project", + "", + "", + "", + "HFSS", + "Maxwell3D", + "Icepak", + "Q3D", + "Maxwell2D", + "Q2D", + "HFSS3DLayout", + "Mechanical", + "Circuit", + "EMIT", + "Simplorer", + "", +] + +window_width, window_height = 500, 250 +screen_width = root.winfo_screenwidth() +screen_height = root.winfo_screenheight() +x_position = (screen_width - window_width) // 2 +y_position = (screen_height - window_height) // 2 + +root.geometry(f"{window_width}x{window_height}+{x_position}+{y_position}") + +# Create buttons in a 4x4 grid, centered +for i, level in enumerate(toolkit_levels): + row_num = i // 4 + col_num = i % 4 + if level: + toolkit_button = ttk.Button( + root, text=level, command=lambda l=level: toolkit_window(l), style="Toolbutton.TButton" + ) + toolkit_button.grid(row=row_num, column=col_num, padx=10, pady=10) + +root.minsize(window_width, window_height) + +root.mainloop() diff --git a/pyaedt/workflows/installer/toolkits_catalog.toml b/pyaedt/workflows/installer/toolkits_catalog.toml new file mode 100644 index 00000000000..a5baf22c440 --- /dev/null +++ b/pyaedt/workflows/installer/toolkits_catalog.toml @@ -0,0 +1,23 @@ +[Console] +name = "PyAEDT Console" +script = "console_setup.py" +icon = "console.png" +template = "PyAEDT_Console" + +[Jupyter] +name = "Jupyter Notebook" +script = "jupyter_template.ipynb" +icon = "jupyter.png" +template = "Jupyter" + +[Run_Script] +name = "Run PyAEDT Script" +script = "" +icon = "run_script.png" +template = "Run_PyAEDT_Script" + +[ToolkitManager] +name = "Toolkit Manager" +script = "toolkit_manager.py" +icon = "toolkit_manager.png" +template = "Run_Toolkit_Manager" diff --git a/pyaedt/workflows/maxwell2d/__init__.py b/pyaedt/workflows/maxwell2d/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/maxwell2d/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/maxwell3d/__init__.py b/pyaedt/workflows/maxwell3d/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/maxwell3d/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/maxwell3d/images/large/magnet_segmentation.png b/pyaedt/workflows/maxwell3d/images/large/magnet_segmentation.png new file mode 100644 index 00000000000..2732690092d Binary files /dev/null and b/pyaedt/workflows/maxwell3d/images/large/magnet_segmentation.png differ diff --git a/pyaedt/workflows/maxwell3d/toolkits_catalog.toml b/pyaedt/workflows/maxwell3d/toolkits_catalog.toml new file mode 100644 index 00000000000..1b403d6e8df --- /dev/null +++ b/pyaedt/workflows/maxwell3d/toolkits_catalog.toml @@ -0,0 +1,7 @@ +[MagnetSegmentationWizard] +name = "Magnet Segmentation Wizard" +script = "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py" +icon = "images/large/magnet_segmentation.png" +template = "Run_PyAEDT_Toolkit_Script" +pip = "ansys-magnet-segmentation-toolkit" +package = "ansys-magnet-segmentation-toolkit" diff --git a/pyaedt/workflows/mechanical/__init__.py b/pyaedt/workflows/mechanical/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/mechanical/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/project/__init__.py b/pyaedt/workflows/project/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/project/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/project/create_report.py b/pyaedt/workflows/project/create_report.py new file mode 100644 index 00000000000..91c99945671 --- /dev/null +++ b/pyaedt/workflows/project/create_report.py @@ -0,0 +1,39 @@ +# Generate pdf report +# ~~~~~~~~~~~~~~~~~~~ +# Generate a pdf report with output of simulation. +import os + +from pyaedt import Desktop +from pyaedt import get_pyaedt_app +from pyaedt.generic.pdf import AnsysReport + +if "PYAEDT_SCRIPT_PORT" in os.environ and "PYAEDT_SCRIPT_VERSION" in os.environ: + port = os.environ["PYAEDT_SCRIPT_PORT"] + version = os.environ["PYAEDT_SCRIPT_VERSION"] +else: + port = 0 + version = "2024.1" + +with Desktop(new_desktop_session=False, close_on_exit=False, specified_version=version, port=port) as d: + + proj = d.active_project() + des = d.active_design() + projname = proj.GetName() + desname = des.GetName() + if des.GetDesignType() in ["HFSS 3D Layout Design", "Circuit Design"]: + desname = None + app = get_pyaedt_app(projname, desname) + + report = AnsysReport(version=d.aedt_version_id, design_name=app.design_name, project_name=app.project_name) + report.create() + report.add_section() + report.add_chapter(f"{app.solution_type} Results") + report.add_sub_chapter("Plots") + report.add_text("This section contains all reports results.") + for plot in app.post.plots: + app.post.export_report_to_jpg(app.working_directory, plot.plot_name) + report.add_image(os.path.join(app.working_directory, plot.plot_name + ".jpg"), plot.plot_name) + report.add_page_break() + report.add_toc() + out = report.save_pdf(app.working_directory, "AEDT_Results.pdf") + d.odesktop.AddMessage("", "", 0, f"Report Generated. {out}") diff --git a/pyaedt/workflows/project/images/large/cad3d.png b/pyaedt/workflows/project/images/large/cad3d.png new file mode 100644 index 00000000000..13e42309060 Binary files /dev/null and b/pyaedt/workflows/project/images/large/cad3d.png differ diff --git a/pyaedt/workflows/project/images/large/pdf.png b/pyaedt/workflows/project/images/large/pdf.png new file mode 100644 index 00000000000..d2647a81bbe Binary files /dev/null and b/pyaedt/workflows/project/images/large/pdf.png differ diff --git a/pyaedt/workflows/project/import_nastran.py b/pyaedt/workflows/project/import_nastran.py new file mode 100644 index 00000000000..a646a5d65dd --- /dev/null +++ b/pyaedt/workflows/project/import_nastran.py @@ -0,0 +1,39 @@ +import os.path + +# import filedialog module +from tkinter import filedialog + +from pyaedt import Desktop +from pyaedt import get_pyaedt_app + + +# Function for opening the +# file explorer window +def browseFiles(): + filename = filedialog.askopenfilename( + initialdir="/", title="Select a File", filetypes=(("Nastran files", "*.nas*"), ("all files", "*.*")) + ) + + # Change label contents + return filename + + +nas_input = browseFiles() +if "PYAEDT_SCRIPT_PORT" in os.environ and "PYAEDT_SCRIPT_VERSION" in os.environ: + port = os.environ["PYAEDT_SCRIPT_PORT"] + version = os.environ["PYAEDT_SCRIPT_VERSION"] +else: + port = 0 + version = "2024.2" +if os.path.exists(nas_input): + with Desktop(new_desktop_session=False, close_on_exit=False, specified_version=version, port=port) as d: + proj = d.active_project() + des = d.active_design() + projname = proj.GetName() + desname = des.GetName() + app = get_pyaedt_app(projname, desname) + app.modeler.import_nastran(nas_input) + d.logger.info("Nastran imported correctly.") +else: + with Desktop(new_desktop_session=False, close_on_exit=False, specified_version=version, port=port) as d: + d.odesktop.AddMessage("", "", 3, "Wrong file selected. Select a .nas file") diff --git a/pyaedt/workflows/project/toolkits_catalog.toml b/pyaedt/workflows/project/toolkits_catalog.toml new file mode 100644 index 00000000000..76270600ced --- /dev/null +++ b/pyaedt/workflows/project/toolkits_catalog.toml @@ -0,0 +1,13 @@ +[GenerateReport] +name = "Generate report" +script = "create_report.py" +icon = "images/large/pdf.png" +template = "Run_PyAEDT_Script" +pip = "" + +[GenerateReport] +name = "Import Nastran" +script = "import_nastran.py" +icon = "images/large/cad3d.png" +template = "Run_PyAEDT_Script" +pip = "" diff --git a/pyaedt/workflows/q2d/__init__.py b/pyaedt/workflows/q2d/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/q2d/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/q3d/__init__.py b/pyaedt/workflows/q3d/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/q3d/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/workflows/simplorer/__init__.py b/pyaedt/workflows/simplorer/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/simplorer/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyaedt/misc/Jupyter.py_build b/pyaedt/workflows/templates/Jupyter.py_build similarity index 100% rename from pyaedt/misc/Jupyter.py_build rename to pyaedt/workflows/templates/Jupyter.py_build diff --git a/pyaedt/misc/Console.py_build b/pyaedt/workflows/templates/PyAEDT_Console.py_build similarity index 97% rename from pyaedt/misc/Console.py_build rename to pyaedt/workflows/templates/PyAEDT_Console.py_build index 60e5b261d74..fb80f53afff 100644 --- a/pyaedt/misc/Console.py_build +++ b/pyaedt/workflows/templates/PyAEDT_Console.py_build @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -* * * This script is meant to run in IronPython within the Ansys Electronics Desktop. * * * +* * * This script is meant to run in IronPython within AEDT. * * * It looks for a reference to a Python interpreter in the ``python_interpreter.bat`` file. diff --git a/pyaedt/misc/Run_PyAEDT_Script.py_build b/pyaedt/workflows/templates/Run_PyAEDT_Script.py_build similarity index 97% rename from pyaedt/misc/Run_PyAEDT_Script.py_build rename to pyaedt/workflows/templates/Run_PyAEDT_Script.py_build index 85bfca277d2..9913717274e 100644 --- a/pyaedt/misc/Run_PyAEDT_Script.py_build +++ b/pyaedt/workflows/templates/Run_PyAEDT_Script.py_build @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -* * * This script is meant to run in IronPython within the Ansys Electronics Desktop. * * * +* * * This script is meant to run in IronPython within AEDT. * * * The script provides for choosing the Python script to execute. It looks for a reference to a Python interpreter in the ``python_interpreter.bat`` file. diff --git a/pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build b/pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build similarity index 92% rename from pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build rename to pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build index 119e1ecced6..f25b98dd282 100644 --- a/pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build +++ b/pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -* * * This script is meant to run in IronPython within the Ansys Electronics Desktop. * * * +* * * This script is meant to run in IronPython within AEDT. * * * The script provides for choosing the Python script to execute. It looks for a reference to a Python interpreter in the ``python_interpreter.bat`` file. @@ -32,7 +32,6 @@ def main(): try: oDesktop.AddMessage("", "", 0, "Toolkit launched. Please wait.") # launch file - version = oDesktop.GetVersion()[2:6].replace(".", "") python_exe = r"##PYTHON_EXE##" % version pyaedt_script = r"##PYTHON_SCRIPT##" check_file(python_exe) @@ -78,7 +77,7 @@ def main(): def check_file(file_path): if not os.path.isfile(file_path): - show_error('"{}" does not exist. Please click on the "Install PyAEDT" button in the Automation ribbon.'.format( + show_error('"{}" does not exist.'.format( file_path)) diff --git a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build new file mode 100644 index 00000000000..4bebbdba872 --- /dev/null +++ b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +* * * This script is meant to run in IronPython within AEDT. * * * +The script provides for choosing the Python script to execute. + +It looks for a reference to a Python interpreter in the ``python_interpreter.bat`` file. + +It then uses this Python interpreter to execute the script. +See the declaration of the command variable to see the order in which arguments are passed to the script. + +The commands allow the launched script to still reference the project and design that was active when the script +was launched as well as the AEDT instance that has them open. + +""" +import os +import sys + +from System.Windows.Forms import MessageBox +from System.Windows.Forms import MessageBoxButtons +from System.Windows.Forms import MessageBoxIcon +from System.Windows.Forms import OpenFileDialog + +is_linux = os.name == "posix" +script_name = os.path.splitext(os.path.basename(__file__))[0] + +if is_linux: + import subprocessdotnet as subprocess +else: + import subprocess + + +def main(): + try: + version = oDesktop.GetVersion()[2:6].replace(".", "") + # launch toolkit manager + python_exe = r"##PYTHON_EXE##" % version + current_dir = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + pyaedt_toolkit_dir = os.path.normpath(os.path.join(current_dir, r"##TOOLKIT_REL_LIB_DIR##")) + pyaedt_script = os.path.join(pyaedt_toolkit_dir, "toolkit_manager.py") + check_file(python_exe) + check_file(pyaedt_script) + os.environ["PYAEDT_SCRIPT_PROCESS_ID"] = str(oDesktop.GetProcessID()) + os.environ["PYAEDT_SCRIPT_VERSION"] = version + if "Ansys Student" in str(oDesktop.GetExeDir()): + os.environ["PYAEDT_STUDENT_VERSION"] = "True" + else: + os.environ["PYAEDT_STUDENT_VERSION"] = "False" + if version > "2022.2": + os.environ["PYAEDT_SCRIPT_PORT"] = str(oDesktop.GetGrpcServerPort()) + if is_linux: + + edt_root = os.path.normpath(oDesktop.GetExeDir()) + os.environ["ANSYSEM_ROOT{}".format(version)] = edt_root + ld_library_path_dirs_to_add = [ + "{}/commonfiles/CPython/3_7/linx64/Release/python/lib".format(edt_root), + "{}/commonfiles/CPython/3_10/linx64/Release/python/lib".format(edt_root), + "{}/common/mono/Linux64/lib64".format(edt_root), + "{}/Delcross".format(edt_root), + "{}".format(edt_root), + ] + os.environ["LD_LIBRARY_PATH"] = ":".join(ld_library_path_dirs_to_add) + ":" + os.getenv( + "LD_LIBRARY_PATH", "") + command = [ + python_exe, + pyaedt_script, + ] + my_env = os.environ.copy() + subprocess.Popen(command, env=my_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) + else: + command = [ + '"{}"'.format(python_exe), + '"{}"'.format(pyaedt_script), + ] + my_env = os.environ.copy() + subprocess.Popen(" ".join(command), env=my_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True) + except Exception as e: + show_error(str(e)) + + +def check_file(file_path): + if not os.path.isfile(file_path): + show_error('"{}" does not exist. Click the "Install PyAEDT" button in the Automation ribbon.'.format( + file_path)) + + +def show_error(msg): + oDesktop.AddMessage("", "", 2, str(msg)) + MessageBox.Show(str(msg), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error) + sys.exit() + + +def debug(msg): + print("[debug] {}: {}".format(script_name, str(msg))) + LogDebug("{}: {}\n".format(script_name, str(msg))) + + +if __name__ == "__main__": + main() diff --git a/pyaedt/workflows/templates/__init__.py b/pyaedt/workflows/templates/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/templates/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 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. diff --git a/pyproject.toml b/pyproject.toml index 64ccc2854c9..23772ac788b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "fpdf2", "jsonschema", "psutil", - "pyedb>=0.4.0,<0.5; python_version == '3.7'", + "pyedb>=0.4.0,<0.10; python_version == '3.7'", "pyedb>=0.5.0; python_version > '3.7'", "pytomlpp; python_version < '3.12'", "rpyc>=6.0.0,<6.1", @@ -38,7 +38,7 @@ dependencies = [ [project.optional-dependencies] tests = [ "imageio>=2.30.0,<2.35", - "ipython>=7.30.0,<8.24", + "ipython>=7.30.0,<8.25", "joblib>=1.0.0,<1.5", "matplotlib>=3.5.0,<3.9", "mock>=5.1.0,<5.2", @@ -46,14 +46,14 @@ tests = [ "openpyxl>=3.1.0,<3.3", "osmnx>=1.1.0,<1.10", "pandas>=1.1.0,<2.3", - "pytest>=7.4.0,<8.2", + "pytest>=7.4.0,<8.3", "pytest-cov>=4.0.0,<5.1", - "pytest-xdist>=3.5.0,<3.6", - "pyedb>=0.4.0,<0.5; python_version == '3.7'", - "pyedb>=0.5.0,<0.8; python_version > '3.7'", + "pytest-xdist>=3.5.0,<3.7", + "pyedb>=0.4.0,<0.10; python_version == '3.7'", + "pyedb>=0.5.0,<0.10; python_version > '3.7'", "pyvista>=0.38.0,<0.44", "scikit-learn>=1.0.0,<1.5", - "scikit-rf>=0.30.0,<0.33", + "scikit-rf>=0.30.0,<1.1", "SRTM.py", "utm", "vtk==9.2.6", @@ -70,7 +70,7 @@ doc = [ "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.24; python_version > '3.7'", + "ipython>=8.13.0,<8.25; python_version > '3.7'", #"ipywidgets>=8.0.0,<8.2", "joblib>=1.3.0,<1.5", "jupyterlab>=4.0.0,<4.3", @@ -84,7 +84,7 @@ doc = [ "pyvista>=0.38.0,<0.44", "recommonmark", #"scikit-learn", - "scikit-rf>=0.30.0,<0.33", + "scikit-rf>=0.30.0,<1.1", "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'", @@ -92,8 +92,7 @@ doc = [ "sphinx-autobuild==2024.4.16; python_version > '3.8'", #"sphinx-autodoc-typehints", "sphinx-copybutton>=0.5.0,<0.6", - "sphinx-gallery>=0.14.0,<0.16", - "sphinx-jinja>=2.0,<2.1", + "sphinx-gallery>=0.14.0,<0.17", #"sphinx-notfound-page", "sphinx_design>=0.4.0,<0.6", #"sphinxcontrib-websupport", @@ -114,11 +113,10 @@ doc-noexamples = [ "sphinx-autobuild==2024.4.16; python_version > '3.8'", #"sphinx-autodoc-typehints", "sphinx-copybutton>=0.5.0,<0.6", - "sphinx-gallery>=0.14.0,<0.16", + "sphinx-gallery>=0.14.0,<0.17", #"sphinx-notfound-page", #"sphinxcontrib-websupport", "sphinx_design>=0.4.0,<0.6", - "sphinx-jinja>=2.0,<2.1", ] all = [ "imageio>=2.30.0,<2.35", @@ -128,7 +126,7 @@ all = [ "osmnx>=1.1.0,<1.10", "pandas>=1.1.0,<2.3", "pyvista>=0.38.0,<0.44", - "scikit-rf>=0.30.0,<0.33", + "scikit-rf>=0.30.0,<1.1", "SRTM.py", "utm", "vtk==9.2.6",