diff --git a/_unittest/test_01_Design.py b/_unittest/test_01_Design.py index c1d1a1c7acd..ae2150c5391 100644 --- a/_unittest/test_01_Design.py +++ b/_unittest/test_01_Design.py @@ -15,6 +15,7 @@ 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": @@ -398,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") 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/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/pyaedt/desktop.py b/pyaedt/desktop.py index 38feb28ff68..2e5ee69272f 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1461,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) @@ -1744,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/generic/general_methods.py b/pyaedt/generic/general_methods.py index 432acb95a12..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) 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/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/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.