From f33984946d8118c2e36daa955fc4b205392ab924 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Mon, 11 Mar 2024 09:40:02 +0100 Subject: [PATCH 01/44] Fix toolkit installer --- pyaedt/misc/toolkit_installer.py | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 pyaedt/misc/toolkit_installer.py diff --git a/pyaedt/misc/toolkit_installer.py b/pyaedt/misc/toolkit_installer.py new file mode 100644 index 00000000000..eb934237625 --- /dev/null +++ b/pyaedt/misc/toolkit_installer.py @@ -0,0 +1,52 @@ +from subprocess import call +import tkinter as tk + + +def uninstall_library(library_name): + call(["pip", "uninstall", library_name]) + + +def create_library_buttons(frame, libraries): + for library in libraries: + button = tk.Button(frame, text=library) + + button.pack(side=tk.TOP, pady=5) + + +def project_window(): + project_window = tk.Toplevel(root) + project_window.title("Project Libraries") + + +libraries = ["library1", "library2", "library3"] + +create_library_buttons(project_window, libraries) + +back_button = tk.Button(project_window, text="Back", command=project_window.destroy) + +back_button.pack(side=tk.BOTTOM, pady=10) + + +def hfss_window(): + hfss_window = tk.Toplevel(root) + hfss_window.title("HFSS Libraries") + + +libraries = ["hfss_library1", "hfss_library2", "hfss_library3"] + +create_library_buttons(hfss_window, libraries) + +back_button.pack(side=tk.BOTTOM, pady=10) + +root = tk.Tk() +root.title("Main Menu") + +project_button = tk.Button(root, text="Project", command=project_window) + +project_button.pack(side=tk.LEFT, padx=10) + +hfss_button = tk.Button(root, text="HFSS", command=hfss_window) + +hfss_button.pack.pack(side=tk.RIGHT, padx=10) + +root.mainloop() From 827088b3bfda67cb17b6910f38aaea9c20aaf682 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 12 Mar 2024 06:56:14 +0100 Subject: [PATCH 02/44] Add tkinter ui for toolkits manager --- pyaedt/misc/images/large/logo.png | Bin 0 -> 35360 bytes pyaedt/misc/toolkit_installer.py | 100 +++++++++++++++++++----------- 2 files changed, 64 insertions(+), 36 deletions(-) create mode 100644 pyaedt/misc/images/large/logo.png diff --git a/pyaedt/misc/images/large/logo.png b/pyaedt/misc/images/large/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..554dc38242dcab3628c396b3e55fbb1342fc2d9a GIT binary patch literal 35360 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 literal 0 HcmV?d00001 diff --git a/pyaedt/misc/toolkit_installer.py b/pyaedt/misc/toolkit_installer.py index eb934237625..0b43a92fc85 100644 --- a/pyaedt/misc/toolkit_installer.py +++ b/pyaedt/misc/toolkit_installer.py @@ -1,52 +1,80 @@ -from subprocess import call import tkinter as tk +from tkinter import ttk - -def uninstall_library(library_name): - call(["pip", "uninstall", library_name]) +# from pyaedt.misc.install_extra_toolkits import available_toolkits def create_library_buttons(frame, libraries): for library in libraries: - button = tk.Button(frame, text=library) - + button = ttk.Button(frame, text=library, style="Toolbutton.TButton") button.pack(side=tk.TOP, pady=5) -def project_window(): - project_window = tk.Toplevel(root) - project_window.title("Project Libraries") - - -libraries = ["library1", "library2", "library3"] - -create_library_buttons(project_window, libraries) - -back_button = tk.Button(project_window, text="Back", command=project_window.destroy) - -back_button.pack(side=tk.BOTTOM, pady=10) +def open_window(window, window_name, libraries): + if not hasattr(window, "opened"): + window.opened = True + window.title(window_name) + create_library_buttons(window, libraries) + root.minsize(400, 300) + else: + window.deiconify() -def hfss_window(): - hfss_window = tk.Toplevel(root) - hfss_window.title("HFSS Libraries") +def toolkit_window(toolkit_level="Project"): + toolkit_window_var = tk.Toplevel(root) + open_window(toolkit_window_var, toolkit_level, ["library1", "library2", "library3"]) -libraries = ["hfss_library1", "hfss_library2", "hfss_library3"] - -create_library_buttons(hfss_window, libraries) - -back_button.pack(side=tk.BOTTOM, pady=10) - root = tk.Tk() -root.title("Main Menu") - -project_button = tk.Button(root, text="Project", command=project_window) - -project_button.pack(side=tk.LEFT, padx=10) - -hfss_button = tk.Button(root, text="HFSS", command=hfss_window) - -hfss_button.pack.pack(side=tk.RIGHT, padx=10) +root.title("AEDT Toolkit Manager") + +# Load the logo for the main window +icon_path = r"images\large\logo.png" +icon = tk.PhotoImage(file=icon_path) + +# Set the icon for the main window +root.iconphoto(True, icon) + +# 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 = 550, 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() From b1d4ab9b7ba4dee4f4ec4f619d0c5575c86959c5 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 12 Mar 2024 14:57:51 +0100 Subject: [PATCH 03/44] Create virtual environment for new toolkits --- pyaedt/desktop.py | 99 ++++++++++++++++++--------- pyaedt/misc/install_extra_toolkits.py | 9 +-- 2 files changed, 66 insertions(+), 42 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 00bcd34a048..5ce4f4427d1 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1669,61 +1669,92 @@ def get_available_toolkits(self): return list(available_toolkits.keys()) @pyaedt_function_handler() - def add_custom_toolkit(self, toolkit_name): # pragma: no cover + def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None): # pragma: no cover """Add toolkit to AEDT Automation Tab. Parameters ---------- toolkit_name : str Name of toolkit to add. + wheel_toolkit : str + Wheelhouse path. Returns ------- bool """ + from pyaedt import is_windows 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 + # Set Python version based on AEDT version + python_version = "3.10" if self.aedt_version_id > "2023.1" else "3.7" - commands = [] - if package_path.startswith("git") and package_name: - commands.append([executable, "-m", "pip", "uninstall", "--yes", package_name]) + base_venv = sys.executable - commands.append([executable, "-m", "pip", "install", "--upgrade", package_path]) + def run_command(command): + if is_windows: + command = '"{}"'.format(command) + ret_code = os.system(command) + return ret_code - 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() + if is_windows: + venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(toolkit_name)) + python_exe = os.path.join(venv_dir, "Scripts", "python.exe") + pip_exe = os.path.join(venv_dir, "Scripts", "pip.exe") + else: + venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(toolkit_name)) + python_exe = os.path.join(venv_dir, "bin", "python") + pip_exe = os.path.join(venv_dir, "bin", "pip") + edt_root = os.path.normpath(self.odesktop.GetExeDir()) + os.environ["ANSYSEM_ROOT{}".format(args.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 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", "") + ) + if not os.path.exists(venv_dir): + run_command('"{}" -m venv "{}" --system-site-packages'.format(base_venv, venv_dir)) + self.logger.info("Virtual environment created {}.".format(toolkit_name)) - install(toolkit["pip"], toolkit.get("package_name", None)) - import site + # Install the specified package + run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) + run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) + else: + 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, - ) + 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 full_path: + # Update toolkit + run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) + else: + # Install toolkit + run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) + + # Install toolkit inside AEDT + + # 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( diff --git a/pyaedt/misc/install_extra_toolkits.py b/pyaedt/misc/install_extra_toolkits.py index fc89a420d04..dfc06eb6dab 100644 --- a/pyaedt/misc/install_extra_toolkits.py +++ b/pyaedt/misc/install_extra_toolkits.py @@ -15,15 +15,8 @@ "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", + "pip": "ansys-magnet-segmentation-toolkit", "image": "pyansys.png", "toolkit_script": "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py", "installation_path": "Maxwell3d", From 5eb2719407586ab1f0db63820e63258a734aa09d Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 12 Mar 2024 15:13:50 +0100 Subject: [PATCH 04/44] Create virtual environment for new toolkits --- pyaedt/misc/toolkit_installer.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyaedt/misc/toolkit_installer.py b/pyaedt/misc/toolkit_installer.py index 0b43a92fc85..a4e9c4d49cc 100644 --- a/pyaedt/misc/toolkit_installer.py +++ b/pyaedt/misc/toolkit_installer.py @@ -15,7 +15,7 @@ def open_window(window, window_name, libraries): window.opened = True window.title(window_name) create_library_buttons(window, libraries) - root.minsize(400, 300) + root.minsize(550, 250) else: window.deiconify() @@ -45,13 +45,14 @@ def toolkit_window(toolkit_level="Project"): "", "", "HFSS", - "Maxwell3d", + "Maxwell3D", "Icepak", - "Q3d", - "Maxwell2d", - "Q2d", + "Q3D", + "Maxwell2D", + "Q2D", "HFSS3DLayout", - "Mechanical" "Circuit", + "Mechanical", + "Circuit", "EMIT", "Simplorer", "", From 68c95d8e10394012fd7f70353a9890b82d65981e Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Wed, 13 Mar 2024 14:45:50 +0100 Subject: [PATCH 05/44] Virtual environment pointed to AEDT ribbon --- pyaedt/desktop.py | 53 +++++++++--------- .../misc/Run_PyAEDT_Toolkit_Script.py_build | 2 +- pyaedt/misc/images/large/antenna.png | Bin 0 -> 1442 bytes .../misc/images/large/magnet_segmentation.png | Bin 0 -> 1605 bytes pyaedt/misc/install_extra_toolkits.py | 8 +-- 5 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 pyaedt/misc/images/large/antenna.png create mode 100644 pyaedt/misc/images/large/magnet_segmentation.png diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 5ce4f4427d1..090547c1c5d 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1730,31 +1730,25 @@ def run_command(command): run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) else: - import site + # Update toolkit + run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) - 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 full_path: - # Update toolkit - run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) - else: - # Install toolkit - run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) - - # Install toolkit inside AEDT - - # 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, - # ) + toolkit_dir = os.path.join(self.personallib, "Toolkits") + tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) + + script_image = os.path.join(os.path.dirname(__file__), "misc", "images", "large", toolkit["image"]) + + if not os.path.exists(tool_dir): + script_path = os.path.join(venv_dir, "Lib", "site-packages", os.path.normpath(toolkit["toolkit_script"])) + # Install toolkit inside AEDT + self.add_script_to_menu( + toolkit_name=toolkit_name, + script_path=script_path, + script_image=script_image, + product=toolkit["installation_path"], + copy_to_personal_lib=False, + executable_interpreter=python_exe, + ) @pyaedt_function_handler() def add_script_to_menu( @@ -1764,7 +1758,7 @@ def add_script_to_menu( script_image=None, product="Project", copy_to_personal_lib=True, - add_pyaedt_desktop_init=True, + executable_interpreter=None, ): """Add a script to the ribbon menu. @@ -1787,6 +1781,8 @@ def add_script_to_menu( 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 : None, optional + Executable python path. The default is the one current interpreter. Returns ------- @@ -1796,6 +1792,11 @@ def add_script_to_menu( if not os.path.exists(script_path): self.logger.error("Script does not exists.") return False + if not executable_interpreter: + executable_version_agnostic = sys.executable + else: + executable_version_agnostic = executable_interpreter + from pyaedt.misc.install_extra_toolkits import write_toolkit_config toolkit_dir = os.path.join(self.personallib, "Toolkits") @@ -1814,7 +1815,7 @@ def add_script_to_menu( 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") diff --git a/pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build b/pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build index 119e1ecced6..9009e21a2f3 100644 --- a/pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build +++ b/pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build @@ -78,7 +78,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/misc/images/large/antenna.png b/pyaedt/misc/images/large/antenna.png new file mode 100644 index 0000000000000000000000000000000000000000..0e77e8b878529a44147d08c1dac13c26806731c9 GIT binary patch literal 1442 zcmb7CX;4#F6h1F63E6RD#d6Z*cmVcWN{-QSPDrnL;+W{V(myU zU>Qq=h(d>AWt>o{RIwF>5>zCym4aBepw{Bju|ic^)BD60|Ja#+cka30JIi;@yK|j= z&g;O9mCB@mVE`Cf;Jk_T$t04M@$xvS?7c-}0V^O-X8~xm2E9Cbfhaj8RpfGgtb&ZC ztMYQm^EE)aBfX?Lzz;KClk;D0W`;UXg%W;4+eeR@qhTi?K4JA3pGtD&7@te>d_%4Q z<;0U*uaHX+&p>?Y>Q}h(6|TzFlldhmXRan^E!m3%IWb4AO-w*118pJXfgGY?0r~&v z8$ovvV9qcAZW{L~D*=ub15CO+?(?YuaNiE_bGXWme1GuID z@NEFBv==dCUNf5rg@kA>9a^g)2h<<}8E8QTGZ71hSr7za&H<1BP9Z^B9Dz!wl3>v3 zG&+;PVlf#^CX3_FVX@uVOr|U0>gLYn5?mIC$LDeRh;vC1jI6||3{=QvGug=dKjAzD z1Op8w!ZB~45ExEi&USbMy*2U~j)TE;p`(Bw7bp}AwFLZS7^C1+8r_8e2G5H>!J8=% z2ay8UzX>ljC^(H2@Ff7%<5UX%k2VsZ5nezIqVtxt@z;1;?9R);LGly=Cm<3kmV1rH z_DvsX4y*Yw<#4<;zCCJqD52u{)W*Iwqx5%gRFpBqMVneg4NERyrJdK#4F)JJhTF&1 z-RoM>ToB*7C-$CzP;AKbDS{E%uPy6-uRUlxq4C?**_0{zT=qq6=(Noj-S#z7FGzmp3wx{LcdEfW2KJ>>X##FUujHr%yHdoF2@}aq^|L5qv9Yg1*b={qjnDT*I z93!qQGtDu6HrsQE$+MPA8H00lKNT5!3%XAaR7dPMqHA99;PKUx(~cR%lNTpOiES3| zbHegKoo4g>_l2^6{kto&-fZP8v@Fvl-Ot}}^3EzrHq?F0;ug|w{^?ky+b2+q2xFwS zZIKhqPkYb26+oOkzQy-e)4)eXyF-m9w!UMOURrXauFV|7;Vm0A)io_R5O*qikJt<@ z4W5UN4tI+Wg-nj~n#)f;ea%v8j=fb9=M(&Pjx{+z;42dr6*x?hcNUl4i0FDSd&;Ml z1i^P6c}liDaEm0Wx4d1T3ToP$Ht|3v=juYUf45q cwi&gTlFEAevjR5)WowOjk;!5EE8E%sH!5SJ!vFvP literal 0 HcmV?d00001 diff --git a/pyaedt/misc/images/large/magnet_segmentation.png b/pyaedt/misc/images/large/magnet_segmentation.png new file mode 100644 index 0000000000000000000000000000000000000000..1b96285dfcefc33b25fcf5514496b760222f1632 GIT binary patch literal 1605 zcmb7AeKeF=9R9sCW6TUPV~`M&nF?((bCPPwHg+(ooiS3#Y?x+<7{*7ng;UFNI-0HY z!HUhsSw+Rx`iP}Y%2%w_#|~=ON3lwXl(yR0`=*ZmY0uf`p7-A8{XNh9-REAdM%xQ? zZx5jdU>E>~259>+jnLhFt5_oP5Uv-@2u*-Mas-Hqj#o)M`7Hl{K$dRr%nn=&k;TQ} z>qmh4KEI212e`HLBcA^%Q@6?EWXLd%K8^|*TV?dOYD3i>P|4kX|SeEk0CA3^y8 zz{)8A;-T4EXbwPS8h}~%Y>jgYz%Uh{;@8>Q?3=`ds6yVhBcYj~PykH)2w)HZ!0Z5^ zZF#Gq=_A=#NU}t^lxT#(4v+&2gb)ofa6pU)j=+T#+8%HRA^`_J6A?&c5)KNPtV5eKY*=uxQ#41+m_bUK4h(>G!o(V2+T@g^9)lSrbVjdVS#9;*INXs-Z+ zf`YM#m<i3mK){Fu9fIzA1tXA%j0K=WHezzu+VG4`_&$=5 zSZz1xqZ|YVkpXTnR2fm@yg9$)`@^a)w~N#v!8wl_Obtt#qzBRmtD{dbhz1EytWNJO^sOehRr17%#NeesHs?%9) z-sM(bqlFcsp~9%gyRxT9UfGX=O-N-;Rwo~R*S9gLscEb6n&Gy z{%j<{gVc5{eLDT2@BRFPG2U}uurE}bzgYQ1sbM$U$OX~ZJ=v)ECe7mJ-@LosE6uou|KRl_v-Jvhy9*rI$g$U3)OB_ zE*Cx8J1KXhnF`;sva`oiU?3o0c|0i5=9iM=mTc?w5k8x@KA7O@nRQq%p8UJ3Y1Mjq(o)AlbzLL- zjo+I7lp@a5=R;a(bQnmSTz)7nNb2HhQE6`eGV+mNDLZrRWzaXxdD rbN7<~`-FKK5SE!sq+ literal 0 HcmV?d00001 diff --git a/pyaedt/misc/install_extra_toolkits.py b/pyaedt/misc/install_extra_toolkits.py index dfc06eb6dab..ce6ea8a3585 100644 --- a/pyaedt/misc/install_extra_toolkits.py +++ b/pyaedt/misc/install_extra_toolkits.py @@ -8,16 +8,16 @@ from pyaedt.misc.aedtlib_personalib_install import write_pretty_xml available_toolkits = { - "AntennaWizard": { + "Antenna Wizard": { "pip": "git+https://github.com/ansys/pyaedt-antenna-toolkit.git", - "image": "pyansys.png", + "image": "antenna.png", "toolkit_script": "ansys/aedt/toolkits/antenna/run_toolkit.py", "installation_path": "HFSS", "package_name": "ansys.aedt.toolkits.antenna", }, - "MagnetSegmentationWizard": { + "Magnet Segmentation Wizard": { "pip": "ansys-magnet-segmentation-toolkit", - "image": "pyansys.png", + "image": "magnet_segmentation.png", "toolkit_script": "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py", "installation_path": "Maxwell3d", "package_name": "magnet-segmentation-toolkit", From da2af278329264f4af8110fa39aa262896b698f0 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Thu, 14 Mar 2024 12:02:27 +0100 Subject: [PATCH 06/44] Uninstall toolkit option --- pyaedt/desktop.py | 51 ++++++++- pyaedt/misc/toolkit_installer.py | 81 -------------- pyaedt/misc/toolkit_manager.py | 183 +++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 84 deletions(-) delete mode 100644 pyaedt/misc/toolkit_installer.py create mode 100644 pyaedt/misc/toolkit_manager.py diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 090547c1c5d..4efcbba8a80 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1687,7 +1687,6 @@ def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None): # pragma: no co from pyaedt.misc.install_extra_toolkits import available_toolkits toolkit = available_toolkits[toolkit_name] - toolkit_name = toolkit_name.replace("_", "") # Set Python version based on AEDT version python_version = "3.10" if self.aedt_version_id > "2023.1" else "3.7" @@ -1724,13 +1723,14 @@ def run_command(command): ) if not os.path.exists(venv_dir): run_command('"{}" -m venv "{}" --system-site-packages'.format(base_venv, venv_dir)) - self.logger.info("Virtual environment created {}.".format(toolkit_name)) - + self.logger.info("Virtual environment for {} toolkit created.".format(toolkit_name)) + self.logger.info("Installing dependencies.".format(toolkit_name)) # Install the specified package run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) else: # Update toolkit + self.logger.info("Installing dependencies.".format(toolkit_name)) run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) toolkit_dir = os.path.join(self.personallib, "Toolkits") @@ -1750,6 +1750,51 @@ def run_command(command): executable_interpreter=python_exe, ) + @pyaedt_function_handler() + def delete_custom_toolkit(self, toolkit_name): # pragma: no cover + """Delete toolkit from AEDT Automation Tab. + + Parameters + ---------- + toolkit_name : str + Name of toolkit to add. + + Returns + ------- + bool + """ + from pyaedt import is_windows + from pyaedt.misc.install_extra_toolkits import available_toolkits + + toolkit = available_toolkits[toolkit_name] + + # Set Python version based on AEDT version + python_version = "3.10" if self.aedt_version_id > "2023.1" else "3.7" + + if is_windows: + venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(toolkit_name)) + else: + venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(toolkit_name)) + + if not os.path.exists(venv_dir): + self.logger.info("Virtual environment {} does not exist.".format(toolkit_name)) + else: + try: + shutil.rmtree(venv_dir) + self.logger.info("Virtual environment {} does deleted.".format(toolkit_name)) + except OSError as e: + self.logger.info(f"Error: {venv_dir} : {e.strerror}") + + toolkit_dir = os.path.join(self.personallib, "Toolkits") + tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) + + if os.path.exists(tool_dir): + # Install toolkit inside AEDT + self.remove_script_from_menu( + toolkit_name=toolkit_name, + product=toolkit["installation_path"], + ) + @pyaedt_function_handler() def add_script_to_menu( self, diff --git a/pyaedt/misc/toolkit_installer.py b/pyaedt/misc/toolkit_installer.py deleted file mode 100644 index a4e9c4d49cc..00000000000 --- a/pyaedt/misc/toolkit_installer.py +++ /dev/null @@ -1,81 +0,0 @@ -import tkinter as tk -from tkinter import ttk - -# from pyaedt.misc.install_extra_toolkits import available_toolkits - - -def create_library_buttons(frame, libraries): - for library in libraries: - button = ttk.Button(frame, text=library, style="Toolbutton.TButton") - button.pack(side=tk.TOP, pady=5) - - -def open_window(window, window_name, libraries): - if not hasattr(window, "opened"): - window.opened = True - window.title(window_name) - create_library_buttons(window, libraries) - root.minsize(550, 250) - else: - window.deiconify() - - -def toolkit_window(toolkit_level="Project"): - toolkit_window_var = tk.Toplevel(root) - open_window(toolkit_window_var, toolkit_level, ["library1", "library2", "library3"]) - - -root = tk.Tk() -root.title("AEDT Toolkit Manager") - -# Load the logo for the main window -icon_path = r"images\large\logo.png" -icon = tk.PhotoImage(file=icon_path) - -# Set the icon for the main window -root.iconphoto(True, icon) - -# 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 = 550, 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/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py new file mode 100644 index 00000000000..a0a1122dd24 --- /dev/null +++ b/pyaedt/misc/toolkit_manager.py @@ -0,0 +1,183 @@ +import os +import tkinter as tk +from tkinter import ttk + +from pyaedt import Desktop +from pyaedt import is_windows +from pyaedt.misc.install_extra_toolkits import available_toolkits + + +def create_library_buttons(frame, libraries): + buttons = [] + + # Create the option menu at the top + option_action = tk.StringVar(value="Install") + install_radio = tk.Radiobutton(frame, text="Install", variable=option_action, value="Install") + uninstall_radio = tk.Radiobutton(frame, text="Uninstall", variable=option_action, value="Uninstall") + install_radio.grid(row=0, column=0, padx=5, pady=5) + uninstall_radio.grid(row=0, column=2, padx=5, pady=5) + + buttons.append(option_action) + + row = 1 + for library in libraries: + if is_windows: + venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(library)) + python_exe = os.path.join(venv_dir, "Scripts", "python.exe") + + else: + venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(library)) + python_exe = os.path.join(venv_dir, "bin", "python") + + # Set the initial button color + button_bg_color = "green" if os.path.isfile(python_exe) else "red" + + button = tk.Button(frame, text=library, bg=button_bg_color, fg="white") + button.grid(row=row, column=1, padx=5, pady=5) + + buttons.append(button) + row += 1 + + return buttons + + +def open_window(window, window_name, libraries): + if not hasattr(window, "opened"): + window.opened = True + window.title(window_name) + buttons = create_library_buttons(window, libraries) + root.minsize(500, 250) + return buttons + else: + window.deiconify() + + +def toolkit_window(toolkit_level="Project"): + toolkit_window_var = tk.Toplevel(root) + specific_toolkits = [] + for toolkit_name, toolkit_info in available_toolkits.items(): + if toolkit_info["installation_path"].lower() == toolkit_level.lower(): + specific_toolkits.append(toolkit_name) + toolkit_window_var.minsize(300, 200) + buttons = open_window(toolkit_window_var, toolkit_level, specific_toolkits) + + option_action = buttons[0] + library_buttons = buttons[1:] + for button in library_buttons: + # Attach event handlers or perform other actions + button.configure(command=lambda b=button, o=option_action: on_button_click(b, o)) + + +def on_button_click(button, option_action): + if is_windows: + venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(button["text"])) + python_exe = os.path.join(venv_dir, "Scripts", "python.exe") + + else: + venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(button["text"])) + python_exe = os.path.join(venv_dir, "bin", "python") + + student_version = False + if ( + "PYAEDT_SCRIPT_VERSION" in os.environ + and "PYAEDT_SCRIPT_PORT" in os.environ + and "PYAEDT_STUDENT_VERSION" in os.environ + ): + + 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 + + desktop = Desktop( + specified_version=version, + port=port, + new_desktop_session=False, + non_graphical=False, + close_on_exit=False, + student_version=student_version, + ) + else: + desktop = Desktop( + new_desktop_session=True, + non_graphical=False, + close_on_exit=False, + student_version=student_version, + ) + + action = option_action.get() + + if os.path.isfile(python_exe) and action == "Install": + desktop.logger.info(f"Updating {button['text']} toolkit.") + desktop.add_custom_toolkit(button["text"]) + elif action == "Install": + desktop.logger.info(f"Installing {button['text']} toolkit.") + desktop.add_custom_toolkit(button["text"]) + elif os.path.isfile(python_exe) and action == "Uninstall": + desktop.logger.info(f"Uninstalling {button['text']} toolkit.") + desktop.delete_custom_toolkit(button["text"]) + else: + desktop.logger.info(f"{button['text']} not installed.") + + if os.path.isfile(python_exe): + button.configure(text=button["text"], bg="green", fg="white") + else: + button.configure(text=button["text"], bg="red", fg="white") + + 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 = r"images\large\logo.png" +icon = tk.PhotoImage(file=icon_path) + +# Set the icon for the main window +root.iconphoto(True, icon) + +# 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() From 0722ce81b759178d6a30c627a06d6984c7902fbe Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Thu, 14 Mar 2024 17:40:18 +0100 Subject: [PATCH 07/44] Wheelhouse --- pyaedt/desktop.py | 27 +++++++++++++++++++++++++-- pyaedt/misc/install_extra_toolkits.py | 2 +- pyaedt/misc/toolkit_manager.py | 27 +++++++++++++++------------ 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 4efcbba8a80..0149cf9c71b 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1721,16 +1721,39 @@ def run_command(command): os.environ["LD_LIBRARY_PATH"] = ( ":".join(ld_library_path_dirs_to_add) + ":" + os.getenv("LD_LIBRARY_PATH", "") ) + new_env = False if not os.path.exists(venv_dir): + new_env = True run_command('"{}" -m venv "{}" --system-site-packages'.format(base_venv, venv_dir)) self.logger.info("Virtual environment for {} toolkit created.".format(toolkit_name)) - self.logger.info("Installing dependencies.".format(toolkit_name)) + + if wheel_toolkit: + wheel_toolkit = os.path.normpath(wheel_toolkit) + self.logger.info("Installing dependencies.".format(toolkit_name)) + if wheel_toolkit and os.path.exists(wheel_toolkit): + if not new_env: + run_command('"{}" uninstall --yes {}'.format(pip_exe, toolkit["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 = available_toolkits[toolkit_name]["package_name"] + run_command( + '"{}" install --no-cache-dir --no-index --find-links={} {}'.format(pip_exe, unzipped_path, package_name) + ) + elif new_env: # Install the specified package run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) else: # Update toolkit - self.logger.info("Installing dependencies.".format(toolkit_name)) run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) toolkit_dir = os.path.join(self.personallib, "Toolkits") diff --git a/pyaedt/misc/install_extra_toolkits.py b/pyaedt/misc/install_extra_toolkits.py index ce6ea8a3585..67fa99b4973 100644 --- a/pyaedt/misc/install_extra_toolkits.py +++ b/pyaedt/misc/install_extra_toolkits.py @@ -20,7 +20,7 @@ "image": "magnet_segmentation.png", "toolkit_script": "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py", "installation_path": "Maxwell3d", - "package_name": "magnet-segmentation-toolkit", + "package_name": "ansys.magnet.segmentation.toolkit", }, } diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index a0a1122dd24..9566b81e352 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -17,8 +17,6 @@ def create_library_buttons(frame, libraries): install_radio.grid(row=0, column=0, padx=5, pady=5) uninstall_radio.grid(row=0, column=2, padx=5, pady=5) - buttons.append(option_action) - row = 1 for library in libraries: if is_windows: @@ -38,16 +36,22 @@ def create_library_buttons(frame, libraries): buttons.append(button) row += 1 - return buttons + # Create entry box for directory path + entry_label = tk.Label(frame, text="Enter Wheelhouse:") + entry_label.grid(row=row, column=0, padx=5, pady=5) + directory_entry = tk.Entry(frame) + directory_entry.grid(row=row, column=1, padx=5, pady=5) + + return buttons, option_action, directory_entry def open_window(window, window_name, libraries): if not hasattr(window, "opened"): window.opened = True window.title(window_name) - buttons = create_library_buttons(window, libraries) + buttons, option_action, directory_entry = create_library_buttons(window, libraries) root.minsize(500, 250) - return buttons + return buttons, option_action, directory_entry else: window.deiconify() @@ -59,16 +63,14 @@ def toolkit_window(toolkit_level="Project"): if toolkit_info["installation_path"].lower() == toolkit_level.lower(): specific_toolkits.append(toolkit_name) toolkit_window_var.minsize(300, 200) - buttons = open_window(toolkit_window_var, toolkit_level, specific_toolkits) + library_buttons, option_action, wheelhouse = open_window(toolkit_window_var, toolkit_level, specific_toolkits) - option_action = buttons[0] - library_buttons = buttons[1:] for button in library_buttons: # Attach event handlers or perform other actions - button.configure(command=lambda b=button, o=option_action: on_button_click(b, o)) + button.configure(command=lambda b=button, o=option_action, w=wheelhouse: on_button_click(b, o, w)) -def on_button_click(button, option_action): +def on_button_click(button, option_action, wheelhouse): if is_windows: venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(button["text"])) python_exe = os.path.join(venv_dir, "Scripts", "python.exe") @@ -105,13 +107,14 @@ def on_button_click(button, option_action): ) action = option_action.get() + wheelhouse_path = wheelhouse.get() if os.path.isfile(python_exe) and action == "Install": desktop.logger.info(f"Updating {button['text']} toolkit.") - desktop.add_custom_toolkit(button["text"]) + desktop.add_custom_toolkit(button["text"], wheelhouse_path) elif action == "Install": desktop.logger.info(f"Installing {button['text']} toolkit.") - desktop.add_custom_toolkit(button["text"]) + desktop.add_custom_toolkit(button["text"], wheelhouse_path) elif os.path.isfile(python_exe) and action == "Uninstall": desktop.logger.info(f"Uninstalling {button['text']} toolkit.") desktop.delete_custom_toolkit(button["text"]) From 09145bbb8234102e60141a7dd21b7dc673c8918f Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 15 Mar 2024 10:37:18 +0100 Subject: [PATCH 08/44] Add button --- pyaedt/misc/Run_Toolkit_Manager.py_build | 94 ++++++++++++++++++++++ pyaedt/misc/aedtlib_personalib_install.py | 17 ++-- pyaedt/misc/images/gallery/PyAEDT.png | Bin 15250 -> 19646 bytes 3 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 pyaedt/misc/Run_Toolkit_Manager.py_build diff --git a/pyaedt/misc/Run_Toolkit_Manager.py_build b/pyaedt/misc/Run_Toolkit_Manager.py_build new file mode 100644 index 00000000000..50d751cd0e6 --- /dev/null +++ b/pyaedt/misc/Run_Toolkit_Manager.py_build @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +""" +* * * This script is meant to run in IronPython within the Ansys Electronics Desktop. * * * +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: + # launch toolkit manager + version = oDesktop.GetVersion()[2:6].replace(".", "") + python_exe = r"##PYTHON_EXE##" % version + pyaedt_script = r"##TOOLKIT_MANAGER_SCRIPT##" + check_file(python_exe) + check_file(pyaedt_script) + os.environ["PYAEDT_SCRIPT_PROCESS_ID"] = str(oDesktop.GetProcessID()) + version = str(oDesktop.GetVersion()[:6]) + os.environ["PYAEDT_SCRIPT_VERSION"] = version + os.environ["PYAEDT_STUDENT_VERSION"] = r"##PYAEDT_STUDENT_VERSION##" + 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) + else: + command = [ + '"{}"'.format(python_exe), + '"{}"'.format(pyaedt_script), + ] + my_env = os.environ.copy() + subprocess.Popen(" ".join(command), env=my_env) + oDesktop.RefreshToolkitUI() + 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. Please click on 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/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py index ccf718acb2a..de767c62bb3 100644 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ b/pyaedt/misc/aedtlib_personalib_install.py @@ -119,23 +119,23 @@ def add_pyaedt_to_aedt( if use_sys_lib: try: sys_dir = os.path.join(sys_dir, "Toolkits") - install_toolkit(sys_dir, product, aedt_version) + install_toolkit(sys_dir, product, aedt_version, is_student_version=is_student_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) + install_toolkit(pers_dir, product, aedt_version, is_student_version=is_student_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) + install_toolkit(pers_dir, product, aedt_version, is_student_version=is_student_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): +def install_toolkit(toolkit_dir, product, aedt_version, is_student_version=False): 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) @@ -148,7 +148,7 @@ def install_toolkit(toolkit_dir, product, aedt_version): 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"] + files_to_copy = ["Console", "Run_PyAEDT_Script", "Jupyter", "Run_Toolkit_Manager"] # 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 @@ -170,6 +170,8 @@ def install_toolkit(toolkit_dir, product, aedt_version): .replace("##PYTHON_EXE##", executable_version_agnostic) .replace("##IPYTHON_EXE##", ipython_executable) .replace("##JUPYTER_EXE##", jupyter_executable) + .replace("##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "toolkit_manager.py")) + .replace("##PYAEDT_STUDENT_VERSION##", str(is_student_version)) ) if not version_agnostic: build_file_data = build_file_data.replace(" % version", "") @@ -179,6 +181,10 @@ def install_toolkit(toolkit_dir, product, aedt_version): os.path.join(current_dir, "jupyter_template.ipynb"), os.path.join(lib_dir, "jupyter_template.ipynb"), ) + shutil.copyfile( + os.path.join(current_dir, "toolkit_manager.py"), + os.path.join(lib_dir, "toolkit_manager.py"), + ) if aedt_version >= "2023.2": write_tab_config(os.path.join(toolkit_dir, product), lib_dir) @@ -214,6 +220,7 @@ def write_tab_config(product_toolkit_dir, pyaedt_lib_dir, force_write=False): 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") + ET.SubElement(group, "button", label="Toolkit Manager", script="PyAEDT/Run Toolkit Manager") # Backup any existing file if present if os.path.isfile(tab_config_file_path): diff --git a/pyaedt/misc/images/gallery/PyAEDT.png b/pyaedt/misc/images/gallery/PyAEDT.png index a51a6cd31aa53293a6d6796d25afab0ae120fa07..621b3237a67b6833790f4d7bac21c991d9efec54 100644 GIT binary patch literal 19646 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 literal 15250 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 From 047ab5c040b72429ec9907eff53bde1fee6ec9f4 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 15 Mar 2024 11:10:26 +0100 Subject: [PATCH 09/44] Add button in run script --- pyaedt/misc/Run_Toolkit_Manager.py_build | 5 ++--- pyaedt/misc/aedtlib_personalib_install.py | 2 +- pyaedt/misc/toolkit_manager.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pyaedt/misc/Run_Toolkit_Manager.py_build b/pyaedt/misc/Run_Toolkit_Manager.py_build index 50d751cd0e6..ff91d21bd78 100644 --- a/pyaedt/misc/Run_Toolkit_Manager.py_build +++ b/pyaedt/misc/Run_Toolkit_Manager.py_build @@ -60,15 +60,14 @@ def main(): pyaedt_script, ] my_env = os.environ.copy() - subprocess.Popen(command, env=my_env) + 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) - oDesktop.RefreshToolkitUI() + 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)) diff --git a/pyaedt/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py index de767c62bb3..5ad09b015c8 100644 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ b/pyaedt/misc/aedtlib_personalib_install.py @@ -228,7 +228,7 @@ def write_tab_config(product_toolkit_dir, pyaedt_lib_dir, force_write=False): write_pretty_xml(root, tab_config_file_path) - files_to_copy = ["images/large/pyansys.png", "images/gallery/PyAEDT.png"] + files_to_copy = ["images/large/pyansys.png", "images/gallery/PyAEDT.png", "images/large/logo.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) diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index 9566b81e352..6d4e9b1ea2a 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -134,7 +134,7 @@ def on_button_click(button, option_action, wheelhouse): root.title("AEDT Toolkit Manager") # Load the logo for the main window -icon_path = r"images\large\logo.png" +icon_path = os.path.join(os.path.dirname(__file__), "images", "large", "logo.png") icon = tk.PhotoImage(file=icon_path) # Set the icon for the main window From ff832826b7c7a222fad5a4294b1118c86c63d197 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 23 Apr 2024 11:50:21 +0200 Subject: [PATCH 10/44] Fix toolkit manager --- pyaedt/desktop.py | 35 ++++- pyaedt/misc/toolkit_manager.py | 250 +++++++++++++++++++++------------ 2 files changed, 191 insertions(+), 94 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 792dbff2a93..a13a6653a54 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1708,7 +1708,32 @@ def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None): # pragma: no co # Set Python version based on AEDT version python_version = "3.10" if self.aedt_version_id > "2023.1" else "3.7" - base_venv = sys.executable + if is_windows: + base_venv = os.path.normpath( + os.path.join( + self.install_path, + "commonfiles", + "CPython", + python_version.replace(".", "_"), + "winx64", + "Release", + "python", + "python.exe", + ) + ) + else: + base_venv = os.path.normpath( + os.path.join( + self.install_path, + "commonfiles", + "CPython", + python_version.replace(".", "_"), + "linx64", + "Release", + "python", + "runpython", + ) + ) def run_command(command): if is_windows: @@ -1716,12 +1741,14 @@ def run_command(command): ret_code = os.system(command) return ret_code + version = self.odesktop.GetVersion()[2:6].replace(".", "") + if is_windows: - venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(toolkit_name)) + venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "toolkits_{}".format(version)) python_exe = os.path.join(venv_dir, "Scripts", "python.exe") pip_exe = os.path.join(venv_dir, "Scripts", "pip.exe") else: - venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(toolkit_name)) + venv_dir = os.path.join(os.environ["HOME"], "pyaedt_env_ide", "toolkits_{}".format(version)) python_exe = os.path.join(venv_dir, "bin", "python") pip_exe = os.path.join(venv_dir, "bin", "pip") edt_root = os.path.normpath(self.odesktop.GetExeDir()) @@ -1742,7 +1769,7 @@ def run_command(command): if not os.path.exists(venv_dir): new_env = True run_command('"{}" -m venv "{}" --system-site-packages'.format(base_venv, venv_dir)) - self.logger.info("Virtual environment for {} toolkit created.".format(toolkit_name)) + self.logger.info("Virtual environment created.") if wheel_toolkit: wheel_toolkit = os.path.normpath(wheel_toolkit) diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index 6d4e9b1ea2a..db78436ce9a 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import os import tkinter as tk from tkinter import ttk @@ -6,90 +7,165 @@ from pyaedt import is_windows from pyaedt.misc.install_extra_toolkits import available_toolkits +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 = None + port = 0 + student_version = False -def create_library_buttons(frame, libraries): - buttons = [] - - # Create the option menu at the top - option_action = tk.StringVar(value="Install") - install_radio = tk.Radiobutton(frame, text="Install", variable=option_action, value="Install") - uninstall_radio = tk.Radiobutton(frame, text="Uninstall", variable=option_action, value="Uninstall") - install_radio.grid(row=0, column=0, padx=5, pady=5) - uninstall_radio.grid(row=0, column=2, padx=5, pady=5) +if is_windows: + venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "toolkits_{}".format(version)) + python_exe = os.path.join(venv_dir, "Scripts", "python.exe") + package_dir = os.path.join(venv_dir, "Lib", "site-packages") - row = 1 - for library in libraries: - if is_windows: - venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(library)) - python_exe = os.path.join(venv_dir, "Scripts", "python.exe") +else: + venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(button["text"])) + python_exe = os.path.join(venv_dir, "bin", "python") + package_dir = os.path.join(venv_dir, "Lib", "site-packages") - else: - venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(library)) - python_exe = os.path.join(venv_dir, "bin", "python") - # Set the initial button color - button_bg_color = "green" if os.path.isfile(python_exe) else "red" +def create_toolkit_page(frame, open_source_toolkits): + """Create page to install toolkit""" + # Available toolkits + toolkits_info = OrderedDict({"Custom": {"is_installed": False}}) + for open_source_toolkit in open_source_toolkits: + script_file = os.path.normpath( + os.path.join(package_dir, available_toolkits[open_source_toolkit]["toolkit_script"]) + ) + is_installed = True if os.path.isfile(script_file) else False + toolkits_info[open_source_toolkit] = {} + toolkits_info[open_source_toolkit]["is_installed"] = is_installed + + max_length = max(len(item) for item in toolkits_info.keys()) + + # 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 != "", list(toolkits_info.keys()))), state="readonly", width=max_length + ) + toolkits_combo.set("Custom") + toolkits_combo.grid(row=2, column=1, padx=5, pady=5) - button = tk.Button(frame, text=library, bg=button_bg_color, fg="white") - button.grid(row=row, 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() + is_installed = toolkits_info[selected_toolkit]["is_installed"] + if is_installed: + 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": + 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") + else: + toolkit_name.config(state="disabled") + input_file_label.config(text="Enter wheelhouse path:") + input_file.config(state="normal") - buttons.append(button) - row += 1 + toolkits_combo.bind("<>", update_page) - # Create entry box for directory path - entry_label = tk.Label(frame, text="Enter Wheelhouse:") - entry_label.grid(row=row, column=0, padx=5, pady=5) - directory_entry = tk.Entry(frame) - directory_entry.grid(row=row, column=1, padx=5, pady=5) + update_page() - return buttons, option_action, directory_entry + return install_button, uninstall_button, installation_option_action, input_file, toolkits_combo, toolkits_info -def open_window(window, window_name, libraries): +def open_window(window, window_name, open_source_toolkits): + """Open window action""" if not hasattr(window, "opened"): window.opened = True window.title(window_name) - buttons, option_action, directory_entry = create_library_buttons(window, libraries) + install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkits_info = ( + create_toolkit_page(window, open_source_toolkits) + ) root.minsize(500, 250) - return buttons, option_action, directory_entry + return install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkits_info else: window.deiconify() +def __get_command_function( + is_install, option_action, input_file, toolkits_combo, toolkits_info, install_button, uninstall_button +): + return lambda: button_is_clicked( + is_install, option_action, input_file, toolkits_combo, toolkits_info, install_button, uninstall_button + ) + + def toolkit_window(toolkit_level="Project"): + """Main window""" toolkit_window_var = tk.Toplevel(root) - specific_toolkits = [] + open_source_toolkits = [] for toolkit_name, toolkit_info in available_toolkits.items(): if toolkit_info["installation_path"].lower() == toolkit_level.lower(): - specific_toolkits.append(toolkit_name) - toolkit_window_var.minsize(300, 200) - library_buttons, option_action, wheelhouse = open_window(toolkit_window_var, toolkit_level, specific_toolkits) - - for button in library_buttons: - # Attach event handlers or perform other actions - button.configure(command=lambda b=button, o=option_action, w=wheelhouse: on_button_click(b, o, w)) - - -def on_button_click(button, option_action, wheelhouse): - if is_windows: - venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(button["text"])) - python_exe = os.path.join(venv_dir, "Scripts", "python.exe") - - else: - venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(button["text"])) - python_exe = os.path.join(venv_dir, "bin", "python") - - student_version = False - if ( - "PYAEDT_SCRIPT_VERSION" in os.environ - and "PYAEDT_SCRIPT_PORT" in os.environ - and "PYAEDT_STUDENT_VERSION" in os.environ - ): - - 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 - + open_source_toolkits.append(toolkit_name) + toolkit_window_var.minsize(250, 150) + install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkits_info = open_window( + toolkit_window_var, toolkit_level, open_source_toolkits + ) + + install_command = __get_command_function( + True, option_action, input_file, toolkits_combo, toolkits_info, install_button, uninstall_button + ) + uninstall_command = __get_command_function( + False, option_action, input_file, toolkits_combo, toolkits_info, install_button, uninstall_button + ) + + install_button.configure(command=install_command) + uninstall_button.configure(command=uninstall_command) + + +def button_is_clicked( + install_action, option_action, input_file, combo_toolkits, toolkits_info, install_button, uninstall_button +): + """Install/Uninstall button action""" + installation_option = option_action.get() + file = input_file.get() + toolkit_name = combo_toolkits.get() + toolkit_info = toolkits_info[toolkit_name] + + if toolkit_name != "Custom": desktop = Desktop( specified_version=version, port=port, @@ -98,36 +174,30 @@ def on_button_click(button, option_action, wheelhouse): close_on_exit=False, student_version=student_version, ) - else: - desktop = Desktop( - new_desktop_session=True, - non_graphical=False, - close_on_exit=False, - student_version=student_version, - ) - action = option_action.get() - wheelhouse_path = wheelhouse.get() - - if os.path.isfile(python_exe) and action == "Install": - desktop.logger.info(f"Updating {button['text']} toolkit.") - desktop.add_custom_toolkit(button["text"], wheelhouse_path) - elif action == "Install": - desktop.logger.info(f"Installing {button['text']} toolkit.") - desktop.add_custom_toolkit(button["text"], wheelhouse_path) - elif os.path.isfile(python_exe) and action == "Uninstall": - desktop.logger.info(f"Uninstalling {button['text']} toolkit.") - desktop.delete_custom_toolkit(button["text"]) - else: - desktop.logger.info(f"{button['text']} not installed.") - - if os.path.isfile(python_exe): - button.configure(text=button["text"], bg="green", fg="white") - else: - button.configure(text=button["text"], bg="red", fg="white") + if toolkit_info.get("is_installed") and install_action: + desktop.logger.info(f"Updating {toolkit_name}.") + desktop.add_custom_toolkit(toolkit_name, file) + install_button.config(text="Update") + uninstall_button.config(state="normal") + desktop.logger.info(f"{toolkit_name} updated") + elif install_action: + desktop.logger.info(f"Installing {toolkit_name}.") + desktop.add_custom_toolkit(toolkit_name, file) + install_button.config(text="Update") + uninstall_button.config(state="normal") + desktop.logger.info(f"{toolkit_name} installed.") + elif toolkit_info.get("is_installed") and not install_action: + desktop.logger.info(f"Uninstalling {toolkit_name}") + desktop.delete_custom_toolkit(toolkit_name) + install_button.config(text="Install") + uninstall_button.config(state="disabled") + desktop.logger.info(f"{toolkit_name} uninstalled") + else: + desktop.logger.info(f"{toolkit_name} not installed.") - desktop.odesktop.RefreshToolkitUI() - desktop.release_desktop(False, False) + desktop.odesktop.RefreshToolkitUI() + desktop.release_desktop(False, False) root = tk.Tk() From 80d5f019a188474bd54229a2afcad8cc683e742f Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 23 Apr 2024 14:40:27 +0200 Subject: [PATCH 11/44] Install wheelhouse --- pyaedt/desktop.py | 112 ++++++++---------- pyaedt/misc/images/large/antenna.png | Bin 1442 -> 1145 bytes .../misc/images/large/magnet_segmentation.png | Bin 1605 -> 1206 bytes pyaedt/misc/toolkit_manager.py | 56 ++++----- 4 files changed, 72 insertions(+), 96 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index a13a6653a54..ac37b9b96ff 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1686,7 +1686,7 @@ def get_available_toolkits(self): return list(available_toolkits.keys()) @pyaedt_function_handler() - def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None): # pragma: no cover + def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None, install=True): # pragma: no cover """Add toolkit to AEDT Automation Tab. Parameters @@ -1695,6 +1695,8 @@ def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None): # pragma: no co Name of toolkit to add. wheel_toolkit : str Wheelhouse path. + install : bool, optional + Whether to install the toolkit. Returns ------- @@ -1747,10 +1749,12 @@ def run_command(command): venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "toolkits_{}".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", "site-packages") else: venv_dir = os.path.join(os.environ["HOME"], "pyaedt_env_ide", "toolkits_{}".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", "site-packages") edt_root = os.path.normpath(self.odesktop.GetExeDir()) os.environ["ANSYSEM_ROOT{}".format(args.version)] = edt_root ld_library_path_dirs_to_add = [ @@ -1765,19 +1769,25 @@ def run_command(command): os.environ["LD_LIBRARY_PATH"] = ( ":".join(ld_library_path_dirs_to_add) + ":" + os.getenv("LD_LIBRARY_PATH", "") ) - new_env = False + + # Create virtual environment + if not os.path.exists(venv_dir): - new_env = True + self.logger.info("Creating virtual environment") run_command('"{}" -m venv "{}" --system-site-packages'.format(base_venv, venv_dir)) self.logger.info("Virtual environment created.") + is_installed = False + script_file = os.path.normpath(os.path.join(package_dir, toolkit["toolkit_script"])) + if os.path.isfile(script_file): + is_installed = True if wheel_toolkit: wheel_toolkit = os.path.normpath(wheel_toolkit) - self.logger.info("Installing dependencies.".format(toolkit_name)) - if wheel_toolkit and os.path.exists(wheel_toolkit): - if not new_env: + self.logger.info("Installing dependencies".format(toolkit_name)) + if install and wheel_toolkit and os.path.exists(wheel_toolkit): + self.logger.info("Starting offline installation".format(toolkit_name)) + if is_installed: run_command('"{}" uninstall --yes {}'.format(pip_exe, toolkit["pip"])) - import zipfile unzipped_path = os.path.join( @@ -1792,75 +1802,49 @@ def run_command(command): run_command( '"{}" install --no-cache-dir --no-index --find-links={} {}'.format(pip_exe, unzipped_path, package_name) ) - elif new_env: + elif install and not is_installed: # Install the specified package run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) - else: + elif not install and is_installed: + # Uninstall toolkit + run_command('"{}" --default-timeout=1000 uninstall -y {}'.format(pip_exe, toolkit["pip"])) + elif install and is_installed: # Update toolkit run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) + else: + self.logger.info("Incorrect input".format(toolkit_name)) + return toolkit_dir = os.path.join(self.personallib, "Toolkits") tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) script_image = os.path.join(os.path.dirname(__file__), "misc", "images", "large", toolkit["image"]) - if not os.path.exists(tool_dir): - script_path = os.path.join(venv_dir, "Lib", "site-packages", os.path.normpath(toolkit["toolkit_script"])) - # Install toolkit inside AEDT - self.add_script_to_menu( - toolkit_name=toolkit_name, - script_path=script_path, - script_image=script_image, - product=toolkit["installation_path"], - copy_to_personal_lib=False, - executable_interpreter=python_exe, - ) - - @pyaedt_function_handler() - def delete_custom_toolkit(self, toolkit_name): # pragma: no cover - """Delete toolkit from AEDT Automation Tab. - - Parameters - ---------- - toolkit_name : str - Name of toolkit to add. - - Returns - ------- - bool - """ - from pyaedt import is_windows - from pyaedt.misc.install_extra_toolkits import available_toolkits - - toolkit = available_toolkits[toolkit_name] - - # Set Python version based on AEDT version - python_version = "3.10" if self.aedt_version_id > "2023.1" else "3.7" - - if is_windows: - venv_dir = os.path.join(os.environ["APPDATA"], "{}_env_ide".format(toolkit_name)) - else: - venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(toolkit_name)) - - if not os.path.exists(venv_dir): - self.logger.info("Virtual environment {} does not exist.".format(toolkit_name)) + if install: + if not os.path.exists(tool_dir): + script_path = os.path.join( + venv_dir, "Lib", "site-packages", os.path.normpath(toolkit["toolkit_script"]) + ) + # Install toolkit inside AEDT + self.add_script_to_menu( + toolkit_name=toolkit_name, + script_path=script_path, + script_image=script_image, + product=toolkit["installation_path"], + copy_to_personal_lib=False, + executable_interpreter=python_exe, + ) else: - try: - shutil.rmtree(venv_dir) - self.logger.info("Virtual environment {} does deleted.".format(toolkit_name)) - except OSError as e: - self.logger.info(f"Error: {venv_dir} : {e.strerror}") - - toolkit_dir = os.path.join(self.personallib, "Toolkits") - tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) - - if os.path.exists(tool_dir): - # Install toolkit inside AEDT - self.remove_script_from_menu( - toolkit_name=toolkit_name, - product=toolkit["installation_path"], - ) + toolkit_dir = os.path.join(self.personallib, "Toolkits") + tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) + + if os.path.exists(tool_dir): + # Install toolkit inside AEDT + self.remove_script_from_menu( + toolkit_name=toolkit_name, + product=toolkit["installation_path"], + ) @pyaedt_function_handler() def add_script_to_menu( diff --git a/pyaedt/misc/images/large/antenna.png b/pyaedt/misc/images/large/antenna.png index 0e77e8b878529a44147d08c1dac13c26806731c9..205f7c54aca8f2c4deeda0009155c9628f86736b 100644 GIT binary patch literal 1145 zcmV-<1cv*GP)Z2XG8hbxJyZcmkop(-oS~8~?ffiP0s5v$SSH|wZYBd_}alwM^Cdi&0435Q3nX=TVL-3<$>8YY^Wx(l`S`{DP%#4-V@szPyjrOFXH z!xj^>0F%k_JQHxD`(!vN>jif2;8A~=JR_$7ED3J-b@ciQ7jE9V4MMzYL02h^#^epG zj;#j|hZ6>aA;|HTfM8F8fx!!qy`xAYKR@_=)KctkY}HQ9&BXpf>@JAPId4JPUw#8X zATR-mDH*KOfyBFDVCcfCy%3m~1m3EsNRTUH7J!LV#yiVRxng zXQA+Z3;)6>w{{1v#9(^c*7 zNa#--M_<%!KPJ*eM5GZm75RL=brzbMZgr;4vE*zU=H!)VwzJrtSE3y}i@#-MoE^h) zah@l1kuxA7?PLwenJG3bL3QKtr_1Zxf5gZx%z(B@1Hm~HNF5s^Pe#BO@v znE;vTE0{?0h)4~Qo;P(2ZA>eyzE;)JxnxSZJX`0#+~oG;6=H#==qC61eCx0voxwyJ zC&c|!wD&NP=ijNQSuv_5Wfrlw z02ZX{e;dJL4vx?+-2yn0@ERsEyE6zQ3+8Q>8N{{j94ltWTL8{vBj00000 LNkvXXu0mjfh#nnn literal 1442 zcmb7CX;4#F6h1F63E6RD#d6Z*cmVcWN{-QSPDrnL;+W{V(myU zU>Qq=h(d>AWt>o{RIwF>5>zCym4aBepw{Bju|ic^)BD60|Ja#+cka30JIi;@yK|j= z&g;O9mCB@mVE`Cf;Jk_T$t04M@$xvS?7c-}0V^O-X8~xm2E9Cbfhaj8RpfGgtb&ZC ztMYQm^EE)aBfX?Lzz;KClk;D0W`;UXg%W;4+eeR@qhTi?K4JA3pGtD&7@te>d_%4Q z<;0U*uaHX+&p>?Y>Q}h(6|TzFlldhmXRan^E!m3%IWb4AO-w*118pJXfgGY?0r~&v z8$ovvV9qcAZW{L~D*=ub15CO+?(?YuaNiE_bGXWme1GuID z@NEFBv==dCUNf5rg@kA>9a^g)2h<<}8E8QTGZ71hSr7za&H<1BP9Z^B9Dz!wl3>v3 zG&+;PVlf#^CX3_FVX@uVOr|U0>gLYn5?mIC$LDeRh;vC1jI6||3{=QvGug=dKjAzD z1Op8w!ZB~45ExEi&USbMy*2U~j)TE;p`(Bw7bp}AwFLZS7^C1+8r_8e2G5H>!J8=% z2ay8UzX>ljC^(H2@Ff7%<5UX%k2VsZ5nezIqVtxt@z;1;?9R);LGly=Cm<3kmV1rH z_DvsX4y*Yw<#4<;zCCJqD52u{)W*Iwqx5%gRFpBqMVneg4NERyrJdK#4F)JJhTF&1 z-RoM>ToB*7C-$CzP;AKbDS{E%uPy6-uRUlxq4C?**_0{zT=qq6=(Noj-S#z7FGzmp3wx{LcdEfW2KJ>>X##FUujHr%yHdoF2@}aq^|L5qv9Yg1*b={qjnDT*I z93!qQGtDu6HrsQE$+MPA8H00lKNT5!3%XAaR7dPMqHA99;PKUx(~cR%lNTpOiES3| zbHegKoo4g>_l2^6{kto&-fZP8v@Fvl-Ot}}^3EzrHq?F0;ug|w{^?ky+b2+q2xFwS zZIKhqPkYb26+oOkzQy-e)4)eXyF-m9w!UMOURrXauFV|7;Vm0A)io_R5O*qikJt<@ z4W5UN4tI+Wg-nj~n#)f;ea%v8j=fb9=M(&Pjx{+z;42dr6*x?hcNUl4i0FDSd&;Ml z1i^P6c}liDaEm0Wx4d1T3ToP$Ht|3v=juYUf45q cwi&gTlFEAevjR5)WowOjk;!5EE8E%sH!5SJ!vFvP diff --git a/pyaedt/misc/images/large/magnet_segmentation.png b/pyaedt/misc/images/large/magnet_segmentation.png index 1b96285dfcefc33b25fcf5514496b760222f1632..2732690092d358c737617d2f6a4fe8e23915cd66 100644 GIT binary patch literal 1206 zcmV;n1WEgeP))^ z6sTQ7Rt2O)=)i1UGw7|(Xsm9qX+Vc#Ted_QtXrhzM{i{mD_dp@W-v5cahs#U63iTw zKBjl+b+`AnGE)~{>?EJ0_uQWIe9v>AABVwsI{xpY==?34Xy~r%QS#2r_b^e+LcuX6 z&A0$vXdNnMfJA(bg<=$#cI?lL3#dpko*-_p;O$P(R5k)q(Ew`11mZ#!hzgafGI34O z6A_@+H{r(PC}q_kEEK!X$n@Y3 zhOH&TkxM0Ao{;bz=gm00+V)T`((FsH!*sZhg<=k-11rgx;oOe~M1{x3r;3hG{mTDx z>JE2*?;=OryvR}2-6|9tuJc4yvk401dnR(UmVsS{j<2%Y`fZ7{WTRd)8^w@KcK>r5 zHk^NGs9AF1MpQQ)qo&_<48-ocu-Q2Y+v|^lSZ)Nlq>_wO4??XOw(dRs_ouOAQKsE{ zn*F|ymDV`76B{f4F-jYBk5Ya$3Ol=lu<5`B5F-tskX2KV$!cJmxtm-kQ13ma-6x?n z&T+a=&;?v7bfFl$dh9B&J4QiJYNC1q_eK@$Z2T-)#Dgq!tblxS>&Wx!@Bg3}QBAA> ze;^F}!7wN%Rw(b)f&0LI?>pGib_V3~8d^_C6h>SmSp+uE{@C>_A%NfGzn;7~=j=SN z9pp62JvB!Wk=u6-f5+9e(0f5r3Awc&P*V0K6f+V6G@je*pZ~NBJu^#iU}_1>o*Q83 zy-a}g1R&et!xt7`yiF1y8T}of-&NmeLt)C})=Lcqve1io?m zq2KL?@oRVB1U5(E-LHfA!Y!W!NCJ?{5AgZy{Vx2pbZe!GH)tA{cYzQP3NL})*bG&> zTOb%-qTQrtbQWJ2|K@a^F{@()_W<2mg{&rh=Nz7kz&Xz(^bb#_^gvs}1D}#TpqYB; zftRf=yb;l@EfQ#{0#I+590N1=pf?nO_CN$$+`qt{lUE3k?jML45TuBKt>!Oq0gaYJ z(AwTb5P#;B{X-1!yZv+Iibu_Y`0xC)Kc~!s99=8Xj+5E(=Hc{Nz)e*L{42m4T)E>~259>+jnLhFt5_oP5Uv-@2u*-Mas-Hqj#o)M`7Hl{K$dRr%nn=&k;TQ} z>qmh4KEI212e`HLBcA^%Q@6?EWXLd%K8^|*TV?dOYD3i>P|4kX|SeEk0CA3^y8 zz{)8A;-T4EXbwPS8h}~%Y>jgYz%Uh{;@8>Q?3=`ds6yVhBcYj~PykH)2w)HZ!0Z5^ zZF#Gq=_A=#NU}t^lxT#(4v+&2gb)ofa6pU)j=+T#+8%HRA^`_J6A?&c5)KNPtV5eKY*=uxQ#41+m_bUK4h(>G!o(V2+T@g^9)lSrbVjdVS#9;*INXs-Z+ zf`YM#m<i3mK){Fu9fIzA1tXA%j0K=WHezzu+VG4`_&$=5 zSZz1xqZ|YVkpXTnR2fm@yg9$)`@^a)w~N#v!8wl_Obtt#qzBRmtD{dbhz1EytWNJO^sOehRr17%#NeesHs?%9) z-sM(bqlFcsp~9%gyRxT9UfGX=O-N-;Rwo~R*S9gLscEb6n&Gy z{%j<{gVc5{eLDT2@BRFPG2U}uurE}bzgYQ1sbM$U$OX~ZJ=v)ECe7mJ-@LosE6uou|KRl_v-Jvhy9*rI$g$U3)OB_ zE*Cx8J1KXhnF`;sva`oiU?3o0c|0i5=9iM=mTc?w5k8x@KA7O@nRQq%p8UJ3Y1Mjq(o)AlbzLL- zjo+I7lp@a5=R;a(bQnmSTz)7nNb2HhQE6`eGV+mNDLZrRWzaXxdD rbN7<~`-FKK5SE!sq+ diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index db78436ce9a..4c1ee789ccf 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -1,4 +1,3 @@ -from collections import OrderedDict import os import tkinter as tk from tkinter import ttk @@ -31,16 +30,8 @@ def create_toolkit_page(frame, open_source_toolkits): """Create page to install toolkit""" # Available toolkits - toolkits_info = OrderedDict({"Custom": {"is_installed": False}}) - for open_source_toolkit in open_source_toolkits: - script_file = os.path.normpath( - os.path.join(package_dir, available_toolkits[open_source_toolkit]["toolkit_script"]) - ) - is_installed = True if os.path.isfile(script_file) else False - toolkits_info[open_source_toolkit] = {} - toolkits_info[open_source_toolkit]["is_installed"] = is_installed - - max_length = max(len(item) for item in toolkits_info.keys()) + toolkits = ["Custom"] + open_source_toolkits + max_length = max(len(item) for item in toolkits) # Pip or Offline radio options installation_option_action = tk.StringVar(value="Offline") @@ -56,7 +47,7 @@ def create_toolkit_page(frame, open_source_toolkits): toolkits_combo_label.grid(row=2, column=0, padx=5, pady=5) toolkits_combo = ttk.Combobox( - frame, values=list(filter(lambda x: x != "", list(toolkits_info.keys()))), state="readonly", width=max_length + 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) @@ -80,8 +71,7 @@ def create_toolkit_page(frame, open_source_toolkits): def update_page(event=None): selected_toolkit = toolkits_combo.get() - is_installed = toolkits_info[selected_toolkit]["is_installed"] - if is_installed: + if is_toolkit_installed(selected_toolkit): install_button.config(text="Update") uninstall_button.config(state="normal") else: @@ -108,7 +98,14 @@ def update_page(event=None): update_page() - return install_button, uninstall_button, installation_option_action, input_file, toolkits_combo, toolkits_info + return install_button, uninstall_button, installation_option_action, input_file, toolkits_combo + + +def is_toolkit_installed(toolkit_name): + if toolkit_name == "Custom": + return False + script_file = os.path.normpath(os.path.join(package_dir, available_toolkits[toolkit_name]["toolkit_script"])) + return True if os.path.isfile(script_file) else False def open_window(window, window_name, open_source_toolkits): @@ -116,20 +113,18 @@ def open_window(window, window_name, open_source_toolkits): if not hasattr(window, "opened"): window.opened = True window.title(window_name) - install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkits_info = ( - create_toolkit_page(window, open_source_toolkits) + install_button, uninstall_button, option_action, input_file, toolkits_combo = create_toolkit_page( + window, open_source_toolkits ) root.minsize(500, 250) - return install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkits_info + return install_button, uninstall_button, option_action, input_file, toolkits_combo else: window.deiconify() -def __get_command_function( - is_install, option_action, input_file, toolkits_combo, toolkits_info, install_button, uninstall_button -): +def __get_command_function(is_install, option_action, input_file, toolkits_combo, install_button, uninstall_button): return lambda: button_is_clicked( - is_install, option_action, input_file, toolkits_combo, toolkits_info, install_button, uninstall_button + is_install, option_action, input_file, toolkits_combo, install_button, uninstall_button ) @@ -141,29 +136,26 @@ def toolkit_window(toolkit_level="Project"): if toolkit_info["installation_path"].lower() == toolkit_level.lower(): open_source_toolkits.append(toolkit_name) toolkit_window_var.minsize(250, 150) - install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkits_info = open_window( + install_button, uninstall_button, option_action, input_file, toolkits_combo = open_window( toolkit_window_var, toolkit_level, open_source_toolkits ) install_command = __get_command_function( - True, option_action, input_file, toolkits_combo, toolkits_info, install_button, uninstall_button + True, option_action, input_file, toolkits_combo, install_button, uninstall_button ) uninstall_command = __get_command_function( - False, option_action, input_file, toolkits_combo, toolkits_info, install_button, uninstall_button + False, option_action, input_file, toolkits_combo, install_button, uninstall_button ) install_button.configure(command=install_command) uninstall_button.configure(command=uninstall_command) -def button_is_clicked( - install_action, option_action, input_file, combo_toolkits, toolkits_info, install_button, uninstall_button -): +def button_is_clicked(install_action, option_action, input_file, combo_toolkits, install_button, uninstall_button): """Install/Uninstall button action""" installation_option = option_action.get() file = input_file.get() toolkit_name = combo_toolkits.get() - toolkit_info = toolkits_info[toolkit_name] if toolkit_name != "Custom": desktop = Desktop( @@ -175,7 +167,7 @@ def button_is_clicked( student_version=student_version, ) - if toolkit_info.get("is_installed") and install_action: + if is_toolkit_installed(toolkit_name) and install_action: desktop.logger.info(f"Updating {toolkit_name}.") desktop.add_custom_toolkit(toolkit_name, file) install_button.config(text="Update") @@ -187,9 +179,9 @@ def button_is_clicked( install_button.config(text="Update") uninstall_button.config(state="normal") desktop.logger.info(f"{toolkit_name} installed.") - elif toolkit_info.get("is_installed") and not install_action: + elif is_toolkit_installed(toolkit_name) and not install_action: desktop.logger.info(f"Uninstalling {toolkit_name}") - desktop.delete_custom_toolkit(toolkit_name) + desktop.add_custom_toolkit(toolkit_name, install=False) install_button.config(text="Install") uninstall_button.config(state="disabled") desktop.logger.info(f"{toolkit_name} uninstalled") From 35631e442d51cb350b597503ee004b539189fbe3 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 26 Apr 2024 10:05:29 +0200 Subject: [PATCH 12/44] Fix venv issue --- pyaedt/desktop.py | 31 ++++-- pyaedt/misc/Run_Toolkit_Manager.py_build | 1 - pyaedt/misc/aedtlib_personalib_install.py | 6 +- pyaedt/misc/toolkit_manager.py | 117 ++++++++++++++-------- 4 files changed, 100 insertions(+), 55 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index ac37b9b96ff..7eb0df2e04b 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1737,21 +1737,34 @@ def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None, install=True): # ) ) + self.logger.info(base_venv) + def run_command(command): - if is_windows: - command = '"{}"'.format(command) - ret_code = os.system(command) - return ret_code + 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) + stdout, 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 = self.odesktop.GetVersion()[2:6].replace(".", "") if is_windows: - venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "toolkits_{}".format(version)) + 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", "site-packages") else: - venv_dir = os.path.join(os.environ["HOME"], "pyaedt_env_ide", "toolkits_{}".format(version)) + 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", "site-packages") @@ -1804,18 +1817,16 @@ def run_command(command): ) elif install and not is_installed: # Install the specified package - run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) elif not install and is_installed: # Uninstall toolkit - run_command('"{}" --default-timeout=1000 uninstall -y {}'.format(pip_exe, toolkit["pip"])) + run_command('"{}" --default-timeout=1000 uninstall -y {}'.format(pip_exe, toolkit["package_name"])) elif install and is_installed: # Update toolkit run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) else: self.logger.info("Incorrect input".format(toolkit_name)) return - toolkit_dir = os.path.join(self.personallib, "Toolkits") tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) @@ -1932,7 +1943,7 @@ def add_script_to_menu( 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)) + self.logger.info("{} installed".format(toolkit_name)) return True @pyaedt_function_handler() diff --git a/pyaedt/misc/Run_Toolkit_Manager.py_build b/pyaedt/misc/Run_Toolkit_Manager.py_build index ff91d21bd78..f32a698966a 100644 --- a/pyaedt/misc/Run_Toolkit_Manager.py_build +++ b/pyaedt/misc/Run_Toolkit_Manager.py_build @@ -32,7 +32,6 @@ else: def main(): try: # launch toolkit manager - version = oDesktop.GetVersion()[2:6].replace(".", "") python_exe = r"##PYTHON_EXE##" % version pyaedt_script = r"##TOOLKIT_MANAGER_SCRIPT##" check_file(python_exe) diff --git a/pyaedt/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py index d57f7c95295..d8515d51ebc 100644 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ b/pyaedt/misc/aedtlib_personalib_install.py @@ -79,9 +79,9 @@ def add_pyaedt_to_aedt( 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() + # desktop = sys.modules["__main__"].oDesktop + pers1 = os.path.join(d.odesktop.GetPersonalLibDirectory(), "pyaedt") + pid = d.odesktop.GetProcessID() # Linking pyaedt in PersonalLib for IronPython compatibility. if os.path.exists(pers1): d.logger.info("PersonalLib already mapped.") diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index 4c1ee789ccf..9feabf8b12f 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -9,15 +9,16 @@ 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"] + version = version[2:6].replace(".", "") port = int(os.environ["PYAEDT_SCRIPT_PORT"]) student_version = False if os.environ["PYAEDT_STUDENT_VERSION"] == "False" else True else: - version = None + version = "2024.1" port = 0 student_version = False if is_windows: - venv_dir = os.path.join(os.environ["APPDATA"], "pyaedt_env_ide", "toolkits_{}".format(version)) + 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") @@ -71,12 +72,16 @@ def create_toolkit_page(frame, open_source_toolkits): def update_page(event=None): selected_toolkit = toolkits_combo.get() - if is_toolkit_installed(selected_toolkit): - install_button.config(text="Update") + if selected_toolkit == "Custom": + install_button.config(text="Install") uninstall_button.config(state="normal") else: - install_button.config(text="Install") - uninstall_button.config(state="disabled") + if is_toolkit_installed(selected_toolkit): + 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": toolkit_name.config(state="disabled") @@ -98,7 +103,7 @@ def update_page(event=None): update_page() - return install_button, uninstall_button, installation_option_action, input_file, toolkits_combo + return install_button, uninstall_button, installation_option_action, input_file, toolkits_combo, toolkit_name def is_toolkit_installed(toolkit_name): @@ -113,18 +118,20 @@ def open_window(window, window_name, open_source_toolkits): if not hasattr(window, "opened"): window.opened = True window.title(window_name) - install_button, uninstall_button, option_action, input_file, toolkits_combo = create_toolkit_page( + install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkit_name = create_toolkit_page( window, open_source_toolkits ) root.minsize(500, 250) - return install_button, uninstall_button, option_action, input_file, toolkits_combo + return install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkit_name else: window.deiconify() -def __get_command_function(is_install, option_action, input_file, toolkits_combo, install_button, uninstall_button): +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, option_action, input_file, toolkits_combo, install_button, uninstall_button + is_install, toolkit_level, input_file, toolkits_combo, toolkit_name, install_button, uninstall_button ) @@ -136,60 +143,88 @@ def toolkit_window(toolkit_level="Project"): if toolkit_info["installation_path"].lower() == toolkit_level.lower(): open_source_toolkits.append(toolkit_name) toolkit_window_var.minsize(250, 150) - install_button, uninstall_button, option_action, input_file, toolkits_combo = open_window( + install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkit_name = open_window( toolkit_window_var, toolkit_level, open_source_toolkits ) install_command = __get_command_function( - True, option_action, input_file, toolkits_combo, install_button, uninstall_button + True, toolkit_level, input_file, toolkits_combo, toolkit_name, install_button, uninstall_button ) uninstall_command = __get_command_function( - False, option_action, input_file, toolkits_combo, install_button, uninstall_button + 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, option_action, input_file, combo_toolkits, install_button, uninstall_button): +def button_is_clicked( + install_action, toolkit_level, input_file, combo_toolkits, toolkit_name, install_button, uninstall_button +): """Install/Uninstall button action""" - installation_option = option_action.get() + # installation_option = option_action.get() file = input_file.get() - toolkit_name = combo_toolkits.get() - - if toolkit_name != "Custom": - desktop = Desktop( - specified_version=version, - port=port, - new_desktop_session=False, - non_graphical=False, - close_on_exit=False, - student_version=student_version, - ) + 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() - if is_toolkit_installed(toolkit_name) and install_action: - desktop.logger.info(f"Updating {toolkit_name}.") - desktop.add_custom_toolkit(toolkit_name, file) + if selected_toolkit_name != "Custom": + desktop.logger.info("TEST: VENV {}".format(venv_dir)) + if is_toolkit_installed(selected_toolkit_name) and install_action: + desktop.logger.info("Updating {}".format(selected_toolkit_name)) + desktop.add_custom_toolkit(selected_toolkit_name, file) install_button.config(text="Update") uninstall_button.config(state="normal") - desktop.logger.info(f"{toolkit_name} updated") + desktop.logger.info("{} updated".format(selected_toolkit_name)) elif install_action: - desktop.logger.info(f"Installing {toolkit_name}.") - desktop.add_custom_toolkit(toolkit_name, file) + desktop.logger.info("Installing {}".format(selected_toolkit_name)) + desktop.add_custom_toolkit(selected_toolkit_name, file) install_button.config(text="Update") uninstall_button.config(state="normal") - desktop.logger.info(f"{toolkit_name} installed.") - elif is_toolkit_installed(toolkit_name) and not install_action: - desktop.logger.info(f"Uninstalling {toolkit_name}") - desktop.add_custom_toolkit(toolkit_name, install=False) + elif is_toolkit_installed(selected_toolkit_name) and not install_action: + desktop.logger.info("Uninstalling {}".format(selected_toolkit_name)) + desktop.add_custom_toolkit(selected_toolkit_name, install=False) install_button.config(text="Install") uninstall_button.config(state="disabled") - desktop.logger.info(f"{toolkit_name} uninstalled") + 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): + desktop.add_script_to_menu( + toolkit_name=name, + script_path=file, + product=toolkit_level, + executable_interpreter=executable_interpreter, + ) + else: + desktop.logger.info("PyAEDT environment is not installed") else: - desktop.logger.info(f"{toolkit_name} not installed.") + desktop.logger.info("Uninstall {}".format(name)) + desktop.remove_script_from_menu(name, product=toolkit_level) - desktop.odesktop.RefreshToolkitUI() - desktop.release_desktop(False, False) + desktop.odesktop.CloseAllWindows() + desktop.odesktop.RefreshToolkitUI() + desktop.release_desktop(False, False) root = tk.Tk() From 3f68e63e6ba859535ec25a8c33f1592b9f87e771 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 26 Apr 2024 14:20:56 +0200 Subject: [PATCH 13/44] Change PyAEDT icons --- pyaedt/misc/aedtlib_personalib_install.py | 48 +++++++++++++++--- pyaedt/misc/images/gallery/PyAEDT.png | Bin 19646 -> 0 bytes pyaedt/misc/images/gallery/console.png | Bin 0 -> 1247 bytes pyaedt/misc/images/gallery/jupyter.png | Bin 0 -> 1920 bytes pyaedt/misc/images/gallery/run_script.png | Bin 0 -> 1845 bytes .../misc/images/gallery/toolkit_manager.png | Bin 0 -> 719 bytes 6 files changed, 40 insertions(+), 8 deletions(-) delete mode 100644 pyaedt/misc/images/gallery/PyAEDT.png create mode 100644 pyaedt/misc/images/gallery/console.png create mode 100644 pyaedt/misc/images/gallery/jupyter.png create mode 100644 pyaedt/misc/images/gallery/run_script.png create mode 100644 pyaedt/misc/images/gallery/toolkit_manager.png diff --git a/pyaedt/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py index d8515d51ebc..c9b54a03e20 100644 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ b/pyaedt/misc/aedtlib_personalib_install.py @@ -211,16 +211,41 @@ def write_tab_config(product_toolkit_dir, pyaedt_lib_dir, force_write=False): # 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") - ET.SubElement(group, "button", label="Toolkit Manager", script="PyAEDT/Run Toolkit Manager") + ET.SubElement( + panel, + "button", + label="Console", + script="PyAEDT/Console", + isLarge="1", + image=image_rel_path + "images/gallery/console.png", + ) + ET.SubElement( + panel, + "button", + label="Jupyter Notebook", + script="PyAEDT/Jupyter", + isLarge="1", + image=image_rel_path + "images/gallery/jupyter.png", + ) + ET.SubElement( + panel, + "button", + label="Run PyAEDT Script", + script="PyAEDT/Run PyAEDT Script", + isLarge="1", + image=image_rel_path + "images/gallery/run_script.png", + ) + ET.SubElement( + panel, + "button", + label="Toolkit Manager", + script="PyAEDT/Run Toolkit Manager", + isLarge="1", + image=image_rel_path + "images/gallery/toolkit_manager.png", + ) # Backup any existing file if present if os.path.isfile(tab_config_file_path): @@ -228,7 +253,14 @@ def write_tab_config(product_toolkit_dir, pyaedt_lib_dir, force_write=False): write_pretty_xml(root, tab_config_file_path) - files_to_copy = ["images/large/pyansys.png", "images/gallery/PyAEDT.png", "images/large/logo.png"] + files_to_copy = [ + "images/large/pyansys.png", + "images/gallery/console.png", + "images/gallery/jupyter.png", + "images/gallery/run_script.png", + "images/gallery/toolkit_manager.png", + "images/large/logo.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) diff --git a/pyaedt/misc/images/gallery/PyAEDT.png b/pyaedt/misc/images/gallery/PyAEDT.png deleted file mode 100644 index 621b3237a67b6833790f4d7bac21c991d9efec54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19646 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 diff --git a/pyaedt/misc/images/gallery/console.png b/pyaedt/misc/images/gallery/console.png new file mode 100644 index 0000000000000000000000000000000000000000..5d22ff8a4c9d43128fe2161e031ee2d6dd84ed96 GIT binary patch literal 1247 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 BO Date: Fri, 26 Apr 2024 14:33:27 +0200 Subject: [PATCH 14/44] Fix Codacy --- pyaedt/desktop.py | 19 ++++++++++--------- pyaedt/misc/toolkit_manager.py | 10 +++++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 7eb0df2e04b..2cbcd7ff8e9 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1702,7 +1702,6 @@ def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None, install=True): # ------- bool """ - from pyaedt import is_windows from pyaedt.misc.install_extra_toolkits import available_toolkits toolkit = available_toolkits[toolkit_name] @@ -1746,8 +1745,10 @@ def run_command(command): command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # nosec else: - process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = process.communicate() + 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")) @@ -1769,7 +1770,7 @@ def run_command(command): pip_exe = os.path.join(venv_dir, "bin", "pip") package_dir = os.path.join(venv_dir, "Lib", "site-packages") edt_root = os.path.normpath(self.odesktop.GetExeDir()) - os.environ["ANSYSEM_ROOT{}".format(args.version)] = edt_root + 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(".", "_") @@ -1777,8 +1778,8 @@ def run_command(command): "{}/common/mono/Linux64/lib64".format(edt_root), "{}".format(edt_root), ] - if args.version < "232": - ld_library_path_dirs_to_add.append("{}/Delcross".format(args.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", "") ) @@ -1796,9 +1797,9 @@ def run_command(command): is_installed = True if wheel_toolkit: wheel_toolkit = os.path.normpath(wheel_toolkit) - self.logger.info("Installing dependencies".format(toolkit_name)) + self.logger.info("Installing dependencies") if install and wheel_toolkit and os.path.exists(wheel_toolkit): - self.logger.info("Starting offline installation".format(toolkit_name)) + self.logger.info("Starting offline installation") if is_installed: run_command('"{}" uninstall --yes {}'.format(pip_exe, toolkit["pip"])) import zipfile @@ -1825,7 +1826,7 @@ def run_command(command): # Update toolkit run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) else: - self.logger.info("Incorrect input".format(toolkit_name)) + self.logger.info("Incorrect input") return toolkit_dir = os.path.join(self.personallib, "Toolkits") tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index 9feabf8b12f..1e4c26b7d83 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -23,7 +23,7 @@ package_dir = os.path.join(venv_dir, "Lib", "site-packages") else: - venv_dir = os.path.join(os.environ["HOME"], "{}_env_ide".format(button["text"])) + 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") @@ -103,7 +103,7 @@ def update_page(event=None): update_page() - return install_button, uninstall_button, installation_option_action, input_file, toolkits_combo, toolkit_name + return install_button, uninstall_button, input_file, toolkits_combo, toolkit_name def is_toolkit_installed(toolkit_name): @@ -118,11 +118,11 @@ def open_window(window, window_name, open_source_toolkits): if not hasattr(window, "opened"): window.opened = True window.title(window_name) - install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkit_name = create_toolkit_page( + install_button, uninstall_button, input_file, toolkits_combo, toolkit_name = create_toolkit_page( window, open_source_toolkits ) root.minsize(500, 250) - return install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkit_name + return install_button, uninstall_button, input_file, toolkits_combo, toolkit_name else: window.deiconify() @@ -143,7 +143,7 @@ def toolkit_window(toolkit_level="Project"): if toolkit_info["installation_path"].lower() == toolkit_level.lower(): open_source_toolkits.append(toolkit_name) toolkit_window_var.minsize(250, 150) - install_button, uninstall_button, option_action, input_file, toolkits_combo, toolkit_name = open_window( + install_button, uninstall_button, input_file, toolkits_combo, toolkit_name = open_window( toolkit_window_var, toolkit_level, open_source_toolkits ) From 10fd91ce6c21877490fba34460f680e2f81a7cd5 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 26 Apr 2024 15:20:57 +0200 Subject: [PATCH 15/44] Fix UT --- _unittest/test_01_toolkit_icons.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/_unittest/test_01_toolkit_icons.py b/_unittest/test_01_toolkit_icons.py index 1cad078d49e..272f3e398c0 100644 --- a/_unittest/test_01_toolkit_icons.py +++ b/_unittest/test_01_toolkit_icons.py @@ -98,7 +98,8 @@ 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) @@ -106,7 +107,12 @@ def validate_file_exists_and_pyaedt_tabs_added(self, file_path): 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"] + files_to_verify = [ + "images/large/pyansys.png", + "images/gallery/console.png", + "images/gallery/run_script.png", + "images/gallery/toolkit_manager.png", + ] for file_name in files_to_verify: assert os.path.isfile(os.path.join(os.path.dirname(file_path), file_name)) return root From 1117397097552b26a99ae790f3028e2b8d6e747b Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Mon, 29 Apr 2024 08:44:35 +0200 Subject: [PATCH 16/44] Add PyAEDT Console --- pyaedt/misc/aedtlib_personalib_install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyaedt/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py index c9b54a03e20..ea009950789 100644 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ b/pyaedt/misc/aedtlib_personalib_install.py @@ -217,7 +217,7 @@ def write_tab_config(product_toolkit_dir, pyaedt_lib_dir, force_write=False): ET.SubElement( panel, "button", - label="Console", + label="PyAEDT Console", script="PyAEDT/Console", isLarge="1", image=image_rel_path + "images/gallery/console.png", From 8d828ef8f162a1a80645bb737d74ed6555535811 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Mon, 29 Apr 2024 08:53:07 +0200 Subject: [PATCH 17/44] Add suggestion --- pyaedt/misc/Console.py_build | 2 +- pyaedt/misc/Run_PyAEDT_Script.py_build | 2 +- pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build | 2 +- pyaedt/misc/Run_Toolkit_Manager.py_build | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyaedt/misc/Console.py_build b/pyaedt/misc/Console.py_build index 60e5b261d74..fb80f53afff 100644 --- a/pyaedt/misc/Console.py_build +++ b/pyaedt/misc/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/misc/Run_PyAEDT_Script.py_build index 85bfca277d2..9913717274e 100644 --- a/pyaedt/misc/Run_PyAEDT_Script.py_build +++ b/pyaedt/misc/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/misc/Run_PyAEDT_Toolkit_Script.py_build index 9009e21a2f3..46b1add3b08 100644 --- a/pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build +++ b/pyaedt/misc/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. diff --git a/pyaedt/misc/Run_Toolkit_Manager.py_build b/pyaedt/misc/Run_Toolkit_Manager.py_build index f32a698966a..79cb6e7fe76 100644 --- a/pyaedt/misc/Run_Toolkit_Manager.py_build +++ b/pyaedt/misc/Run_Toolkit_Manager.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. From 63b7c9a2a3b37c5b59ee9f2e062c362661781d26 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Mon, 29 Apr 2024 08:57:06 +0200 Subject: [PATCH 18/44] Add suggestion --- pyaedt/misc/toolkit_manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index 1e4c26b7d83..a441f9f7664 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -29,7 +29,7 @@ def create_toolkit_page(frame, open_source_toolkits): - """Create page to install toolkit""" + """Create page to display toolkit on.""" # Available toolkits toolkits = ["Custom"] + open_source_toolkits max_length = max(len(item) for item in toolkits) @@ -107,6 +107,7 @@ def update_page(event=None): def is_toolkit_installed(toolkit_name): + """Check if toolkit is installed.""" if toolkit_name == "Custom": return False script_file = os.path.normpath(os.path.join(package_dir, available_toolkits[toolkit_name]["toolkit_script"])) @@ -136,7 +137,7 @@ def __get_command_function( def toolkit_window(toolkit_level="Project"): - """Main window""" + """Create interactive toolkit window.""" toolkit_window_var = tk.Toplevel(root) open_source_toolkits = [] for toolkit_name, toolkit_info in available_toolkits.items(): @@ -161,8 +162,7 @@ def toolkit_window(toolkit_level="Project"): def button_is_clicked( install_action, toolkit_level, input_file, combo_toolkits, toolkit_name, install_button, uninstall_button ): - """Install/Uninstall button action""" - # installation_option = option_action.get() + """Set up a button for installing and uninstalling the toolkit.""" file = input_file.get() selected_toolkit_name = combo_toolkits.get() name = toolkit_name.get() From 0b958c47a6214e887cdd6c041d380f6744553491 Mon Sep 17 00:00:00 2001 From: Samuel Lopez <85613111+Samuelopez-ansys@users.noreply.github.com> Date: Mon, 29 Apr 2024 08:57:17 +0200 Subject: [PATCH 19/44] Apply suggestions from code review Co-authored-by: Kathy Pippert <84872299+PipKat@users.noreply.github.com> --- pyaedt/misc/Run_Toolkit_Manager.py_build | 2 +- pyaedt/misc/aedtlib_personalib_install.py | 2 +- pyaedt/misc/toolkit_manager.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyaedt/misc/Run_Toolkit_Manager.py_build b/pyaedt/misc/Run_Toolkit_Manager.py_build index f32a698966a..577a13f6307 100644 --- a/pyaedt/misc/Run_Toolkit_Manager.py_build +++ b/pyaedt/misc/Run_Toolkit_Manager.py_build @@ -73,7 +73,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. Click the "Install PyAEDT" button in the Automation ribbon.'.format( file_path)) diff --git a/pyaedt/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py index c9b54a03e20..bc4dfca095b 100644 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ b/pyaedt/misc/aedtlib_personalib_install.py @@ -149,7 +149,7 @@ def install_toolkit(toolkit_dir, product, aedt_version, is_student_version=False os.makedirs(lib_dir, exist_ok=True) os.makedirs(tool_dir, exist_ok=True) files_to_copy = ["Console", "Run_PyAEDT_Script", "Jupyter", "Run_Toolkit_Manager"] - # Remove hard-coded version number from Python virtual environment path, and replace it with the corresponding AEDT + # 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: diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index 1e4c26b7d83..27c2ccfb2b7 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -114,7 +114,7 @@ def is_toolkit_installed(toolkit_name): def open_window(window, window_name, open_source_toolkits): - """Open window action""" + """Open a window.""" if not hasattr(window, "opened"): window.opened = True window.title(window_name) @@ -217,9 +217,9 @@ def button_is_clicked( executable_interpreter=executable_interpreter, ) else: - desktop.logger.info("PyAEDT environment is not installed") + desktop.logger.info("PyAEDT environment is not installed.") else: - desktop.logger.info("Uninstall {}".format(name)) + desktop.logger.info("Uninstall {}.".format(name)) desktop.remove_script_from_menu(name, product=toolkit_level) desktop.odesktop.CloseAllWindows() From ea5463e5a90e27b441a9cbea919fa7f3b0048292 Mon Sep 17 00:00:00 2001 From: samuel Date: Mon, 29 Apr 2024 13:25:18 +0200 Subject: [PATCH 20/44] Add TK and TCK env variables in Linux --- doc/source/Resources/PyAEDTInstallerFromDesktop.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/source/Resources/PyAEDTInstallerFromDesktop.py b/doc/source/Resources/PyAEDTInstallerFromDesktop.py index 8513222d0d9..5651dcd6a05 100644 --- a/doc/source/Resources/PyAEDTInstallerFromDesktop.py +++ b/doc/source/Resources/PyAEDTInstallerFromDesktop.py @@ -119,6 +119,12 @@ 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 +145,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)) From 10f381cb3ee174b453f3b89ff35e0143502d711e Mon Sep 17 00:00:00 2001 From: samuel Date: Mon, 29 Apr 2024 13:25:58 +0200 Subject: [PATCH 21/44] Add TK and TCK env variables in Linux --- doc/source/Resources/PyAEDTInstallerFromDesktop.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/source/Resources/PyAEDTInstallerFromDesktop.py b/doc/source/Resources/PyAEDTInstallerFromDesktop.py index 5651dcd6a05..414841db492 100644 --- a/doc/source/Resources/PyAEDTInstallerFromDesktop.py +++ b/doc/source/Resources/PyAEDTInstallerFromDesktop.py @@ -119,12 +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( - ".", "_")) + 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): From e878fb2dd1206ca7e27e94ecf39ba20ea700027b Mon Sep 17 00:00:00 2001 From: samuel Date: Mon, 29 Apr 2024 14:24:37 +0200 Subject: [PATCH 22/44] Fix Linux logo --- pyaedt/desktop.py | 2 +- pyaedt/misc/toolkit_manager.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 2cbcd7ff8e9..8ef6847a92a 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1889,7 +1889,7 @@ def add_script_to_menu( 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 : None, optional + executable_interpreter : str, optional Executable python path. The default is the one current interpreter. Returns diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/misc/toolkit_manager.py index ab7db3fd31b..eb2dacfabbd 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/misc/toolkit_manager.py @@ -2,6 +2,9 @@ import tkinter as tk from tkinter import ttk +import PIL.Image +import PIL.ImageTk + from pyaedt import Desktop from pyaedt import is_windows from pyaedt.misc.install_extra_toolkits import available_toolkits @@ -232,10 +235,11 @@ def button_is_clicked( # Load the logo for the main window icon_path = os.path.join(os.path.dirname(__file__), "images", "large", "logo.png") -icon = tk.PhotoImage(file=icon_path) +im = PIL.Image.open(icon_path) +photo = PIL.ImageTk.PhotoImage(im) # Set the icon for the main window -root.iconphoto(True, icon) +root.iconphoto(True, photo) # Configure style for ttk buttons style = ttk.Style() From e88138819bc897ce076c0ac7c3856a0a4e0e2cb9 Mon Sep 17 00:00:00 2001 From: samuel Date: Mon, 29 Apr 2024 16:09:33 +0200 Subject: [PATCH 23/44] Linux compatibility --- pyaedt/desktop.py | 18 +++++++++++------- pyaedt/misc/Run_Toolkit_Manager.py_build | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index 8ef6847a92a..d67746f5a5b 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1763,12 +1763,12 @@ def run_command(command): 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", "site-packages") + 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", "site-packages") + package_dir = os.path.join(venv_dir, "lib") edt_root = os.path.normpath(self.odesktop.GetExeDir()) os.environ["ANSYSEM_ROOT{}".format(version)] = edt_root ld_library_path_dirs_to_add = [ @@ -1792,7 +1792,14 @@ def run_command(command): self.logger.info("Virtual environment created.") is_installed = False - script_file = os.path.normpath(os.path.join(package_dir, toolkit["toolkit_script"])) + script_file = None + if os.path.isdir(os.path.normpath(os.path.join(package_dir, toolkit["toolkit_script"]))): + script_file = os.path.normpath(os.path.join(package_dir, toolkit["toolkit_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["toolkit_script"])) + break if os.path.isfile(script_file): is_installed = True if wheel_toolkit: @@ -1835,13 +1842,10 @@ def run_command(command): if install: if not os.path.exists(tool_dir): - script_path = os.path.join( - venv_dir, "Lib", "site-packages", os.path.normpath(toolkit["toolkit_script"]) - ) # Install toolkit inside AEDT self.add_script_to_menu( toolkit_name=toolkit_name, - script_path=script_path, + script_path=script_file, script_image=script_image, product=toolkit["installation_path"], copy_to_personal_lib=False, diff --git a/pyaedt/misc/Run_Toolkit_Manager.py_build b/pyaedt/misc/Run_Toolkit_Manager.py_build index 4f1ec929735..ca3d1b9cfe5 100644 --- a/pyaedt/misc/Run_Toolkit_Manager.py_build +++ b/pyaedt/misc/Run_Toolkit_Manager.py_build @@ -32,6 +32,7 @@ else: def main(): try: # launch toolkit manager + version = oDesktop.GetVersion()[2:6].replace(".", "") python_exe = r"##PYTHON_EXE##" % version pyaedt_script = r"##TOOLKIT_MANAGER_SCRIPT##" check_file(python_exe) From 5e82308c4c9beb0b5775dba2a305ca2e19dec5cc Mon Sep 17 00:00:00 2001 From: samuel Date: Mon, 29 Apr 2024 16:40:16 +0200 Subject: [PATCH 24/44] Fix Maxwell name --- pyaedt/misc/install_extra_toolkits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyaedt/misc/install_extra_toolkits.py b/pyaedt/misc/install_extra_toolkits.py index 67fa99b4973..2ed8b9f86e8 100644 --- a/pyaedt/misc/install_extra_toolkits.py +++ b/pyaedt/misc/install_extra_toolkits.py @@ -19,7 +19,7 @@ "pip": "ansys-magnet-segmentation-toolkit", "image": "magnet_segmentation.png", "toolkit_script": "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py", - "installation_path": "Maxwell3d", + "installation_path": "Maxwell3D", "package_name": "ansys.magnet.segmentation.toolkit", }, } From 2c33572376de20b0084461ac6a894a17428fbcce Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 30 Apr 2024 11:44:27 +0200 Subject: [PATCH 25/44] Workflows directory --- pyaedt/misc/aedtlib_personalib_install.py | 11 +- pyaedt/workflows/__init__.py | 21 ++ pyaedt/workflows/circuit/__init__.py | 21 ++ pyaedt/workflows/customize_automation_tab.py | 280 ++++++++++++++++++ pyaedt/workflows/emit/__init__.py | 21 ++ pyaedt/workflows/hfss/__init__.py | 21 ++ pyaedt/workflows/hfss3dlayout/__init__.py | 21 ++ pyaedt/workflows/icepak/__init__.py | 21 ++ pyaedt/workflows/images/large/pyansys.png | Bin 0 -> 855 bytes pyaedt/workflows/maxwell2d/__init__.py | 21 ++ pyaedt/workflows/maxwell3d/__init__.py | 21 ++ pyaedt/workflows/mechanical/__init__.py | 21 ++ pyaedt/workflows/project/__init__.py | 21 ++ .../project}/console_setup.py | 2 +- .../project/images/large/console.png | Bin 0 -> 1247 bytes .../project/images/large/jupyter.png | Bin 0 -> 1920 bytes .../project/images/large/run_script.png | Bin 0 -> 1845 bytes .../project/images/large/toolkit_manager.png | Bin 0 -> 719 bytes pyaedt/workflows/project/pyaedt_installer.py | 204 +++++++++++++ pyaedt/workflows/q2d/__init__.py | 21 ++ pyaedt/workflows/q3d/__init__.py | 21 ++ pyaedt/workflows/simplorer/__init__.py | 21 ++ .../templates}/Jupyter.py_build | 0 .../templates/PyAEDT_Console.py_build} | 0 .../templates}/Run_PyAEDT_Script.py_build | 0 .../templates}/Run_Toolkit_Manager.py_build | 0 pyaedt/workflows/templates/__init__.py | 21 ++ pyaedt/{misc => workflows}/toolkit_manager.py | 24 +- 28 files changed, 809 insertions(+), 6 deletions(-) create mode 100644 pyaedt/workflows/__init__.py create mode 100644 pyaedt/workflows/circuit/__init__.py create mode 100644 pyaedt/workflows/customize_automation_tab.py create mode 100644 pyaedt/workflows/emit/__init__.py create mode 100644 pyaedt/workflows/hfss/__init__.py create mode 100644 pyaedt/workflows/hfss3dlayout/__init__.py create mode 100644 pyaedt/workflows/icepak/__init__.py create mode 100644 pyaedt/workflows/images/large/pyansys.png create mode 100644 pyaedt/workflows/maxwell2d/__init__.py create mode 100644 pyaedt/workflows/maxwell3d/__init__.py create mode 100644 pyaedt/workflows/mechanical/__init__.py create mode 100644 pyaedt/workflows/project/__init__.py rename pyaedt/{misc => workflows/project}/console_setup.py (97%) create mode 100644 pyaedt/workflows/project/images/large/console.png create mode 100644 pyaedt/workflows/project/images/large/jupyter.png create mode 100644 pyaedt/workflows/project/images/large/run_script.png create mode 100644 pyaedt/workflows/project/images/large/toolkit_manager.png create mode 100644 pyaedt/workflows/project/pyaedt_installer.py create mode 100644 pyaedt/workflows/q2d/__init__.py create mode 100644 pyaedt/workflows/q3d/__init__.py create mode 100644 pyaedt/workflows/simplorer/__init__.py rename pyaedt/{misc => workflows/templates}/Jupyter.py_build (100%) rename pyaedt/{misc/Console.py_build => workflows/templates/PyAEDT_Console.py_build} (100%) rename pyaedt/{misc => workflows/templates}/Run_PyAEDT_Script.py_build (100%) rename pyaedt/{misc => workflows/templates}/Run_Toolkit_Manager.py_build (100%) create mode 100644 pyaedt/workflows/templates/__init__.py rename pyaedt/{misc => workflows}/toolkit_manager.py (89%) diff --git a/pyaedt/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py index 5e76fe59a37..b70df1ad616 100644 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ b/pyaedt/misc/aedtlib_personalib_install.py @@ -170,20 +170,23 @@ def install_toolkit(toolkit_dir, product, aedt_version, is_student_version=False .replace("##PYTHON_EXE##", executable_version_agnostic) .replace("##IPYTHON_EXE##", ipython_executable) .replace("##JUPYTER_EXE##", jupyter_executable) - .replace("##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "toolkit_manager.py")) + .replace("##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "../workflows/toolkit_manager.py")) .replace("##PYAEDT_STUDENT_VERSION##", str(is_student_version)) ) 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, "../workflows/project/console_setup.py"), + os.path.join(lib_dir, "../workflows/project/console_setup.py"), + ) shutil.copyfile( os.path.join(current_dir, "jupyter_template.ipynb"), os.path.join(lib_dir, "jupyter_template.ipynb"), ) shutil.copyfile( - os.path.join(current_dir, "toolkit_manager.py"), - os.path.join(lib_dir, "toolkit_manager.py"), + os.path.join(current_dir, "../workflows/toolkit_manager.py"), + os.path.join(lib_dir, "../workflows/toolkit_manager.py"), ) if aedt_version >= "2023.2": write_tab_config(os.path.join(toolkit_dir, product), lib_dir) 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..667ed7f2651 --- /dev/null +++ b/pyaedt/workflows/customize_automation_tab.py @@ -0,0 +1,280 @@ +# 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 sys +from xml.dom.minidom import parseString +import xml.etree.ElementTree as ET +from xml.etree.ElementTree import ParseError + +from pyaedt import is_linux +import pyaedt.workflows.templates + +"""Methods to add new automation tabs in AEDT.""" + + +def add_automation_tab( + name, lib_dir, icon_file=None, product="Project", template="Run PyAEDT Toolkit Script", overwrite=False +): + """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. + + """ + + 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) + 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 name 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) + + 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) + + ET.SubElement( + panel, + "button", + label=name, + isLarge="1", + image=dest_file, + 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) + + +def remove_automation_tab(name, lib_dir): + """Remove automation tab in AEDT. + + Parameters + ---------- + name : str + Toolkit name. + lib_dir : str + Path to the library directory. + + 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) + 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 name 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) + + 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 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, +): + """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. + + Returns + ------- + bool + + """ + if 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_dir = os.path.join(toolkit_dir, product, 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) + + if 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__) + + # Console + ipython_executable = executable_version_agnostic.replace("python" + __exe(), "ipython" + __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).replace( + "##IPYTHON_EXE##", ipython_executable + ) + if not version_agnostic: + build_file_data = build_file_data.replace(" % version", "") + out_file.write(build_file_data) + + if aedt_version >= "2023.2": + if not icon_file: + icon_file = os.path.join(os.path.dirname(__file__), "images", "large", "pyansys.png") + add_automation_tab(name, toolkit_dir, icon_file=icon_file, product=product, template=file_name_dest) + desktop_object.logger.info("{} installed".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/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/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/pyansys.png b/pyaedt/workflows/images/large/pyansys.png new file mode 100644 index 0000000000000000000000000000000000000000..f02292deeb72a7dd5da83466b01e1772d4dad86b GIT binary patch literal 855 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 BO= "2023.2": + # write_tab_config(os.path.join(toolkit_dir, product), lib_dir) 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 100% rename from pyaedt/misc/Console.py_build rename to pyaedt/workflows/templates/PyAEDT_Console.py_build diff --git a/pyaedt/misc/Run_PyAEDT_Script.py_build b/pyaedt/workflows/templates/Run_PyAEDT_Script.py_build similarity index 100% rename from pyaedt/misc/Run_PyAEDT_Script.py_build rename to pyaedt/workflows/templates/Run_PyAEDT_Script.py_build diff --git a/pyaedt/misc/Run_Toolkit_Manager.py_build b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build similarity index 100% rename from pyaedt/misc/Run_Toolkit_Manager.py_build rename to pyaedt/workflows/templates/Run_Toolkit_Manager.py_build diff --git a/pyaedt/workflows/templates/__init__.py b/pyaedt/workflows/templates/__init__.py new file mode 100644 index 00000000000..3bc3c8e5b19 --- /dev/null +++ b/pyaedt/workflows/templates/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. diff --git a/pyaedt/misc/toolkit_manager.py b/pyaedt/workflows/toolkit_manager.py similarity index 89% rename from pyaedt/misc/toolkit_manager.py rename to pyaedt/workflows/toolkit_manager.py index eb2dacfabbd..854607a0c86 100644 --- a/pyaedt/misc/toolkit_manager.py +++ b/pyaedt/workflows/toolkit_manager.py @@ -1,3 +1,25 @@ +# 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 @@ -234,7 +256,7 @@ def button_is_clicked( root.title("AEDT Toolkit Manager") # Load the logo for the main window -icon_path = os.path.join(os.path.dirname(__file__), "images", "large", "logo.png") +icon_path = os.path.join(os.path.dirname(__file__), "../misc/images", "large", "logo.png") im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) From 665fdff1a79696bb2c9c9240b1a603c1418d040c Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 30 Apr 2024 14:54:40 +0200 Subject: [PATCH 26/44] Workflows directory --- .../Resources/PyAEDTInstallerFromDesktop.py | 6 +- pyaedt/misc/aedtlib_personalib_install.py | 12 +- pyaedt/workflows/customize_automation_tab.py | 17 +- .../project}/jupyter_template.ipynb | 0 pyaedt/workflows/project/pyaedt_installer.py | 211 ++++++------------ .../{ => project}/toolkit_manager.py | 2 +- .../workflows/project/toolkits_catalog.toml | 23 ++ .../Run_PyAEDT_Toolkit_Script.py_build | 0 .../templates/Run_Toolkit_Manager.py_build | 3 +- 9 files changed, 112 insertions(+), 162 deletions(-) rename pyaedt/{misc => workflows/project}/jupyter_template.ipynb (100%) rename pyaedt/workflows/{ => project}/toolkit_manager.py (99%) create mode 100644 pyaedt/workflows/project/toolkits_catalog.toml rename pyaedt/{misc => workflows/templates}/Run_PyAEDT_Toolkit_Script.py_build (100%) diff --git a/doc/source/Resources/PyAEDTInstallerFromDesktop.py b/doc/source/Resources/PyAEDTInstallerFromDesktop.py index 414841db492..d074c20c09c 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.project.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) diff --git a/pyaedt/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py index b70df1ad616..52d635c708f 100644 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ b/pyaedt/misc/aedtlib_personalib_install.py @@ -170,7 +170,9 @@ def install_toolkit(toolkit_dir, product, aedt_version, is_student_version=False .replace("##PYTHON_EXE##", executable_version_agnostic) .replace("##IPYTHON_EXE##", ipython_executable) .replace("##JUPYTER_EXE##", jupyter_executable) - .replace("##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "../workflows/toolkit_manager.py")) + .replace( + "##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "../workflows/project/toolkit_manager.py") + ) .replace("##PYAEDT_STUDENT_VERSION##", str(is_student_version)) ) if not version_agnostic: @@ -181,12 +183,12 @@ def install_toolkit(toolkit_dir, product, aedt_version, is_student_version=False os.path.join(lib_dir, "../workflows/project/console_setup.py"), ) shutil.copyfile( - os.path.join(current_dir, "jupyter_template.ipynb"), - os.path.join(lib_dir, "jupyter_template.ipynb"), + os.path.join(current_dir, "../workflows/project/jupyter_template.ipynb"), + os.path.join(lib_dir, "../workflows/project/jupyter_template.ipynb"), ) shutil.copyfile( - os.path.join(current_dir, "../workflows/toolkit_manager.py"), - os.path.join(lib_dir, "../workflows/toolkit_manager.py"), + os.path.join(current_dir, "../workflows/project/toolkit_manager.py"), + os.path.join(lib_dir, "../workflows/project/toolkit_manager.py"), ) if aedt_version >= "2023.2": write_tab_config(os.path.join(toolkit_dir, product), lib_dir) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index 667ed7f2651..192a286f150 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -83,7 +83,7 @@ def add_automation_tab( 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"] == toolkitname][0] + b = [button for button in buttons if button.attrib["label"] == name][0] panel.remove(b) file_name = os.path.basename(icon_file) @@ -220,7 +220,8 @@ def add_script_to_menu( bool """ - if not os.path.exists(script_file): + + if script_file and not os.path.exists(script_file): desktop_object.logger.error("Script does not exists.") return False @@ -236,7 +237,7 @@ def add_script_to_menu( os.makedirs(lib_dir, exist_ok=True) os.makedirs(tool_dir, exist_ok=True) - if copy_to_personal_lib: + 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) @@ -252,15 +253,19 @@ def add_script_to_menu( templates_dir = os.path.dirname(pyaedt.workflows.templates.__file__) - # Console 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).replace( - "##IPYTHON_EXE##", ipython_executable + 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) + build_file_data = build_file_data.replace( + "##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "project/toolkit_manager.py") ) if not version_agnostic: build_file_data = build_file_data.replace(" % version", "") diff --git a/pyaedt/misc/jupyter_template.ipynb b/pyaedt/workflows/project/jupyter_template.ipynb similarity index 100% rename from pyaedt/misc/jupyter_template.ipynb rename to pyaedt/workflows/project/jupyter_template.ipynb diff --git a/pyaedt/workflows/project/pyaedt_installer.py b/pyaedt/workflows/project/pyaedt_installer.py index 5c3242a46e7..38e2bdadd2d 100644 --- a/pyaedt/workflows/project/pyaedt_installer.py +++ b/pyaedt/workflows/project/pyaedt_installer.py @@ -26,17 +26,15 @@ 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, - use_sys_lib=False, new_desktop_session=False, non_graphical=False, - sys_dir="", - pers_dir="", ): """Add PyAEDT tabs in AEDT. @@ -47,158 +45,79 @@ def add_pyaedt_to_aedt( student_version : bool, optional Whether to use the student version of AEDT. The default is ``False``. - use_sys_lib : bool, optional - Whether to use the ``syslib`` or ``PersonalLib`` directory. 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``. - sys_dir : str, optional - Full path of ``syslib`` directory. - pers_dir : str, optional - Full path of ``PersonalLib`` directory. - """ - 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, 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 + + 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: - 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.") + if is_windows: + os.system('mklink /D "{}" "{}"'.format(pers1, pyaedt_path)) 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 - - toolkits = ["Project"] - # Bug on Linux 23.1 and before where Project level toolkits don't show up. Thus copying to individual design - # toolkits. - if not is_windows 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") - __add_pyaedt_tabs(d, sys_dir, product, aedt_version, student_version) - print("Installed toolkit for {} in sys lib.".format(product)) - except IOError: - pers_dir = os.path.join(pers_dir, "Toolkits") - __add_pyaedt_tabs(d, pers_dir, product, aedt_version, student_version) - print("Installed toolkit for {} in PersonalLib.".format(product)) - else: - pers_dir = os.path.join(pers_dir, "Toolkits") - __add_pyaedt_tabs(d, pers_dir, product, aedt_version, student_version) - print("Installed toolkit for {} in PersonalLib.".format(product)) - - if pid and new_desktop_session: - try: - os.kill(pid, 9) - except Exception: - pass - - -def __add_pyaedt_tabs(desktop_object, input_dir, product, aedt_version, student_version=False): - """Add PyAEDT tabs in AEDT. + 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: + pass + + +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")) - Parameters - ---------- - desktop_object : :class:pyaedt.desktop.Desktop - Desktop object. - input_dir : str - Path to the toolkit library directory. - product : str, optional - Product directory to install the toolkit. - aedt_version : str, optional - Version of AEDT to use. - student_version : bool, optional - Whether to use the student version of AEDT. The default - is ``False``. - """ project_workflows_dir = os.path.dirname(__file__) - script_path = os.path.join(project_workflows_dir, "console_setup.py") - icon_file = os.path.join(project_workflows_dir, "images", "large", "console.png") - template_name = "PyAEDT_Console" - customize_automation_tab.add_script_to_menu( - desktop_object, - "PyAEDT Console", - script_path, - template_name, - icon_file=icon_file, - product="Project", - copy_to_personal_lib=True, - executable_interpreter=None, - ) - - # files_to_copy = ["Console", "Run_PyAEDT_Script", "Jupyter", "Run_Toolkit_Manager"] - # # Remove hard-coded version number from Python virtual environment path and replace it with the corresponding AEDT - # # version's Python virtual environment. - # - # jupyter_executable = executable_version_agnostic.replace("python" + exe(), "jupyter" + 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) - # .replace("##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "../workflows/toolkit_manager.py")) - # .replace("##PYAEDT_STUDENT_VERSION##", str(is_student_version)) - # ) - # 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"), - # ) - # shutil.copyfile( - # os.path.join(current_dir, "../workflows/toolkit_manager.py"), - # os.path.join(lib_dir, "../workflows/toolkit_manager.py"), - # ) - # if aedt_version >= "2023.2": - # write_tab_config(os.path.join(toolkit_dir, product), lib_dir) + 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, + ) diff --git a/pyaedt/workflows/toolkit_manager.py b/pyaedt/workflows/project/toolkit_manager.py similarity index 99% rename from pyaedt/workflows/toolkit_manager.py rename to pyaedt/workflows/project/toolkit_manager.py index 854607a0c86..3b0382172f5 100644 --- a/pyaedt/workflows/toolkit_manager.py +++ b/pyaedt/workflows/project/toolkit_manager.py @@ -256,7 +256,7 @@ def button_is_clicked( root.title("AEDT Toolkit Manager") # Load the logo for the main window -icon_path = os.path.join(os.path.dirname(__file__), "../misc/images", "large", "logo.png") +icon_path = os.path.join(os.path.dirname(__file__), "../../misc/images", "large", "logo.png") im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) diff --git a/pyaedt/workflows/project/toolkits_catalog.toml b/pyaedt/workflows/project/toolkits_catalog.toml new file mode 100644 index 00000000000..a5baf22c440 --- /dev/null +++ b/pyaedt/workflows/project/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/misc/Run_PyAEDT_Toolkit_Script.py_build b/pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build similarity index 100% rename from pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build rename to pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build diff --git a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build index ca3d1b9cfe5..0f6821d3c48 100644 --- a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build +++ b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build @@ -40,7 +40,8 @@ def main(): os.environ["PYAEDT_SCRIPT_PROCESS_ID"] = str(oDesktop.GetProcessID()) version = str(oDesktop.GetVersion()[:6]) os.environ["PYAEDT_SCRIPT_VERSION"] = version - os.environ["PYAEDT_STUDENT_VERSION"] = r"##PYAEDT_STUDENT_VERSION##" + if "Ansys Student" in str(oDesktop.GetExeDir()): + os.environ["PYAEDT_STUDENT_VERSION"] = True if version > "2022.2": os.environ["PYAEDT_SCRIPT_PORT"] = str(oDesktop.GetGrpcServerPort()) if is_linux: From ed834a9deae4cf6f06f052bc132677010672a7ae Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 30 Apr 2024 15:16:21 +0200 Subject: [PATCH 27/44] Remove old scripts --- .../Resources/PyAEDTInstallerFromDesktop.py | 20 +- pyaedt/misc/aedtlib_personalib_install.py | 294 ------------------ pyaedt/misc/install_extra_toolkits.py | 125 -------- 3 files changed, 1 insertion(+), 438 deletions(-) delete mode 100644 pyaedt/misc/aedtlib_personalib_install.py delete mode 100644 pyaedt/misc/install_extra_toolkits.py diff --git a/doc/source/Resources/PyAEDTInstallerFromDesktop.py b/doc/source/Resources/PyAEDTInstallerFromDesktop.py index d074c20c09c..a38df11744b 100644 --- a/doc/source/Resources/PyAEDTInstallerFromDesktop.py +++ b/doc/source/Resources/PyAEDTInstallerFromDesktop.py @@ -156,14 +156,10 @@ 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 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)) @@ -185,20 +181,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/misc/aedtlib_personalib_install.py b/pyaedt/misc/aedtlib_personalib_install.py deleted file mode 100644 index 52d635c708f..00000000000 --- a/pyaedt/misc/aedtlib_personalib_install.py +++ /dev/null @@ -1,294 +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(d.odesktop.GetPersonalLibDirectory(), "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)) - 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, is_student_version=is_student_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, is_student_version=is_student_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, is_student_version=is_student_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, is_student_version=False): - 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", "Run_Toolkit_Manager"] - # 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) - .replace( - "##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "../workflows/project/toolkit_manager.py") - ) - .replace("##PYAEDT_STUDENT_VERSION##", str(is_student_version)) - ) - 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, "../workflows/project/console_setup.py"), - os.path.join(lib_dir, "../workflows/project/console_setup.py"), - ) - shutil.copyfile( - os.path.join(current_dir, "../workflows/project/jupyter_template.ipynb"), - os.path.join(lib_dir, "../workflows/project/jupyter_template.ipynb"), - ) - shutil.copyfile( - os.path.join(current_dir, "../workflows/project/toolkit_manager.py"), - os.path.join(lib_dir, "../workflows/project/toolkit_manager.py"), - ) - 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") - image_rel_path = os.path.relpath(pyaedt_lib_dir, product_toolkit_dir).replace("\\", "/") + "/" - if image_rel_path == "./": - image_rel_path = "" - ET.SubElement( - panel, - "button", - label="PyAEDT Console", - script="PyAEDT/Console", - isLarge="1", - image=image_rel_path + "images/gallery/console.png", - ) - ET.SubElement( - panel, - "button", - label="Jupyter Notebook", - script="PyAEDT/Jupyter", - isLarge="1", - image=image_rel_path + "images/gallery/jupyter.png", - ) - ET.SubElement( - panel, - "button", - label="Run PyAEDT Script", - script="PyAEDT/Run PyAEDT Script", - isLarge="1", - image=image_rel_path + "images/gallery/run_script.png", - ) - ET.SubElement( - panel, - "button", - label="Toolkit Manager", - script="PyAEDT/Run Toolkit Manager", - isLarge="1", - image=image_rel_path + "images/gallery/toolkit_manager.png", - ) - - # 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/console.png", - "images/gallery/jupyter.png", - "images/gallery/run_script.png", - "images/gallery/toolkit_manager.png", - "images/large/logo.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/install_extra_toolkits.py b/pyaedt/misc/install_extra_toolkits.py deleted file mode 100644 index 2ed8b9f86e8..00000000000 --- a/pyaedt/misc/install_extra_toolkits.py +++ /dev/null @@ -1,125 +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 = { - "Antenna Wizard": { - "pip": "git+https://github.com/ansys/pyaedt-antenna-toolkit.git", - "image": "antenna.png", - "toolkit_script": "ansys/aedt/toolkits/antenna/run_toolkit.py", - "installation_path": "HFSS", - "package_name": "ansys.aedt.toolkits.antenna", - }, - "Magnet Segmentation Wizard": { - "pip": "ansys-magnet-segmentation-toolkit", - "image": "magnet_segmentation.png", - "toolkit_script": "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py", - "installation_path": "Maxwell3D", - "package_name": "ansys.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) From 2d6e8302b262ae4e3046221a2bfec7668cc5792d Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 30 Apr 2024 15:23:44 +0200 Subject: [PATCH 28/44] Remove old scripts --- pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build | 1 - pyaedt/workflows/templates/Run_Toolkit_Manager.py_build | 1 - 2 files changed, 2 deletions(-) diff --git a/pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build b/pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build index 46b1add3b08..f25b98dd282 100644 --- a/pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build +++ b/pyaedt/workflows/templates/Run_PyAEDT_Toolkit_Script.py_build @@ -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) diff --git a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build index 0f6821d3c48..9f1a8a4712d 100644 --- a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build +++ b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build @@ -32,7 +32,6 @@ else: def main(): try: # launch toolkit manager - version = oDesktop.GetVersion()[2:6].replace(".", "") python_exe = r"##PYTHON_EXE##" % version pyaedt_script = r"##TOOLKIT_MANAGER_SCRIPT##" check_file(python_exe) From dcb68264408b2d87ba55b81fd26dce4a1e8ae38a Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 30 Apr 2024 15:28:38 +0200 Subject: [PATCH 29/44] Remove toolkit manager env --- pyaedt/workflows/customize_automation_tab.py | 4 +--- pyaedt/workflows/templates/Run_Toolkit_Manager.py_build | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index 192a286f150..bc83b8159ae 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -264,9 +264,7 @@ def add_script_to_menu( 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) - build_file_data = build_file_data.replace( - "##TOOLKIT_MANAGER_SCRIPT##", os.path.join(lib_dir, "project/toolkit_manager.py") - ) + if not version_agnostic: build_file_data = build_file_data.replace(" % version", "") out_file.write(build_file_data) diff --git a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build index 9f1a8a4712d..8002154e5d3 100644 --- a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build +++ b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build @@ -33,7 +33,7 @@ def main(): try: # launch toolkit manager python_exe = r"##PYTHON_EXE##" % version - pyaedt_script = r"##TOOLKIT_MANAGER_SCRIPT##" + 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()) From 9f3b82204cc03d23a4ba9758dfb98e5d4433a3b7 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Tue, 30 Apr 2024 17:11:14 +0200 Subject: [PATCH 30/44] Install custom toolkit --- pyaedt/desktop.py | 293 ------------------ pyaedt/misc/images/gallery/console.png | Bin 1247 -> 0 bytes pyaedt/misc/images/gallery/jupyter.png | Bin 1920 -> 0 bytes pyaedt/misc/images/gallery/run_script.png | Bin 1845 -> 0 bytes .../misc/images/gallery/toolkit_manager.png | Bin 719 -> 0 bytes pyaedt/misc/images/large/pyansys.png | Bin 855 -> 0 bytes pyaedt/workflows/customize_automation_tab.py | 263 +++++++++++++++- .../hfss}/images/large/antenna.png | Bin pyaedt/workflows/hfss/toolkits_catalog.toml | 6 + .../images/large/magnet_segmentation.png | Bin .../workflows/maxwell3d/toolkits_catalog.toml | 6 + .../project}/images/large/logo.png | Bin pyaedt/workflows/project/toolkit_manager.py | 65 ++-- 13 files changed, 313 insertions(+), 320 deletions(-) delete mode 100644 pyaedt/misc/images/gallery/console.png delete mode 100644 pyaedt/misc/images/gallery/jupyter.png delete mode 100644 pyaedt/misc/images/gallery/run_script.png delete mode 100644 pyaedt/misc/images/gallery/toolkit_manager.png delete mode 100644 pyaedt/misc/images/large/pyansys.png rename pyaedt/{misc => workflows/hfss}/images/large/antenna.png (100%) create mode 100644 pyaedt/workflows/hfss/toolkits_catalog.toml rename pyaedt/{misc => workflows/maxwell3d}/images/large/magnet_segmentation.png (100%) create mode 100644 pyaedt/workflows/maxwell3d/toolkits_catalog.toml rename pyaedt/{misc => workflows/project}/images/large/logo.png (100%) diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index d67746f5a5b..6c0fe517059 100644 --- a/pyaedt/desktop.py +++ b/pyaedt/desktop.py @@ -1685,299 +1685,6 @@ def get_available_toolkits(self): return list(available_toolkits.keys()) - @pyaedt_function_handler() - def add_custom_toolkit(self, toolkit_name, wheel_toolkit=None, install=True): # pragma: no cover - """Add toolkit to AEDT Automation Tab. - - Parameters - ---------- - toolkit_name : str - Name of toolkit to add. - wheel_toolkit : str - Wheelhouse path. - install : bool, optional - Whether to install the toolkit. - - Returns - ------- - bool - """ - from pyaedt.misc.install_extra_toolkits import available_toolkits - - toolkit = available_toolkits[toolkit_name] - - # Set Python version based on AEDT version - python_version = "3.10" if self.aedt_version_id > "2023.1" else "3.7" - - if is_windows: - base_venv = os.path.normpath( - os.path.join( - self.install_path, - "commonfiles", - "CPython", - python_version.replace(".", "_"), - "winx64", - "Release", - "python", - "python.exe", - ) - ) - else: - base_venv = os.path.normpath( - os.path.join( - self.install_path, - "commonfiles", - "CPython", - python_version.replace(".", "_"), - "linx64", - "Release", - "python", - "runpython", - ) - ) - - self.logger.info(base_venv) - - 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 = self.odesktop.GetVersion()[2:6].replace(".", "") - - 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") - 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(self.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): - self.logger.info("Creating virtual environment") - run_command('"{}" -m venv "{}" --system-site-packages'.format(base_venv, venv_dir)) - self.logger.info("Virtual environment created.") - - is_installed = False - script_file = None - if os.path.isdir(os.path.normpath(os.path.join(package_dir, toolkit["toolkit_script"]))): - script_file = os.path.normpath(os.path.join(package_dir, toolkit["toolkit_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["toolkit_script"])) - break - if os.path.isfile(script_file): - is_installed = True - if wheel_toolkit: - wheel_toolkit = os.path.normpath(wheel_toolkit) - self.logger.info("Installing dependencies") - if install and wheel_toolkit and os.path.exists(wheel_toolkit): - self.logger.info("Starting offline installation") - if is_installed: - run_command('"{}" uninstall --yes {}'.format(pip_exe, toolkit["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 = available_toolkits[toolkit_name]["package_name"] - 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["pip"])) - elif not install and is_installed: - # Uninstall toolkit - run_command('"{}" --default-timeout=1000 uninstall -y {}'.format(pip_exe, toolkit["package_name"])) - elif install and is_installed: - # Update toolkit - run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) - else: - self.logger.info("Incorrect input") - return - toolkit_dir = os.path.join(self.personallib, "Toolkits") - tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) - - script_image = os.path.join(os.path.dirname(__file__), "misc", "images", "large", toolkit["image"]) - - if install: - if not os.path.exists(tool_dir): - # Install toolkit inside AEDT - self.add_script_to_menu( - toolkit_name=toolkit_name, - script_path=script_file, - script_image=script_image, - product=toolkit["installation_path"], - copy_to_personal_lib=False, - executable_interpreter=python_exe, - ) - else: - toolkit_dir = os.path.join(self.personallib, "Toolkits") - tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) - - if os.path.exists(tool_dir): - # Install toolkit inside AEDT - self.remove_script_from_menu( - toolkit_name=toolkit_name, - product=toolkit["installation_path"], - ) - - @pyaedt_function_handler() - def add_script_to_menu( - self, - toolkit_name, - script_path, - script_image=None, - product="Project", - copy_to_personal_lib=True, - executable_interpreter=None, - ): - """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``. - executable_interpreter : str, optional - Executable python path. The default is the one current interpreter. - - Returns - ------- - bool - - """ - if not os.path.exists(script_path): - self.logger.error("Script does not exists.") - return False - if not executable_interpreter: - executable_version_agnostic = sys.executable - else: - executable_version_agnostic = executable_interpreter - - 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"] - - 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("{} 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/misc/images/gallery/console.png b/pyaedt/misc/images/gallery/console.png deleted file mode 100644 index 5d22ff8a4c9d43128fe2161e031ee2d6dd84ed96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1247 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 h@VK4@%mV%&z*iKG6ksd "2023.1" else "3.7" + + if is_windows: + base_venv = os.path.normpath( + os.path.join( + self.install_path, + "commonfiles", + "CPython", + python_version.replace(".", "_"), + "winx64", + "Release", + "python", + "python.exe", + ) + ) + else: + base_venv = os.path.normpath( + os.path.join( + self.install_path, + "commonfiles", + "CPython", + python_version.replace(".", "_"), + "linx64", + "Release", + "python", + "runpython", + ) + ) + + desktop_object.logger.info(base_venv) + + 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 = self.odesktop.GetVersion()[2:6].replace(".", "") + + 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") + 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["toolkit_script"]))): + script_file = os.path.normpath(os.path.join(package_dir, toolkit["toolkit_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["toolkit_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["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 = available_toolkits[toolkit_name]["package_name"] + 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["pip"])) + elif not install and is_installed: + # Uninstall toolkit + run_command('"{}" --default-timeout=1000 uninstall -y {}'.format(pip_exe, toolkit["package_name"])) + elif install and is_installed: + # Update toolkit + run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["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, toolkit["installation_path"], toolkit_name) + + script_image = os.path.join(os.path.dirname(__file__), "misc", "images", "large", toolkit["image"]) + + if install: + if not os.path.exists(tool_dir): + # Install toolkit inside AEDT + add_script_to_menu( + desktop_object=desktop_object, + name=toolkit_name, + script_file=script_file, + icon_file=script_image, + product=toolkit["installation_path"], + template_file="Run_PyAEDT_Toolkit_Script", + copy_to_personal_lib=False, + executable_interpreter=python_exe, + ) + else: + toolkit_dir = os.path.join(self.personallib, "Toolkits") + tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) + + if os.path.exists(tool_dir): + # Install toolkit inside AEDT + self.remove_script_from_menu( + toolkit_name=toolkit_name, + product=toolkit["installation_path"], + ) + + +def remove_script_from_menu(desktop_object, toolkit_name, product="Project"): + """Remove a toolkit script from the menu. + + Parameters + ---------- + desktop_object : :class:pyaedt.desktop.Desktop + Desktop object. + 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 + """ + + toolkit_dir = os.path.join(desktop_object.personallib, "Toolkits") + aedt_version = desktop_object.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_xml_tab(os.path.join(toolkit_dir, product), toolkit_name) + desktop_object.logger.info("{} toolkit removed successfully.".format(toolkit_name)) + return True + + def __exe(): if not is_linux: return ".exe" diff --git a/pyaedt/misc/images/large/antenna.png b/pyaedt/workflows/hfss/images/large/antenna.png similarity index 100% rename from pyaedt/misc/images/large/antenna.png rename to pyaedt/workflows/hfss/images/large/antenna.png diff --git a/pyaedt/workflows/hfss/toolkits_catalog.toml b/pyaedt/workflows/hfss/toolkits_catalog.toml new file mode 100644 index 00000000000..8b1360e69ab --- /dev/null +++ b/pyaedt/workflows/hfss/toolkits_catalog.toml @@ -0,0 +1,6 @@ +[AntennaWizard] +name = "Antenna Wizard" +script = "ansys/aedt/toolkits/antenna/run_toolkit.py" +icon = "antenna.png" +template = "Run_PyAEDT_Toolkit_Script" +pip = "git+https://github.com/ansys/pyaedt-antenna-toolkit.git" diff --git a/pyaedt/misc/images/large/magnet_segmentation.png b/pyaedt/workflows/maxwell3d/images/large/magnet_segmentation.png similarity index 100% rename from pyaedt/misc/images/large/magnet_segmentation.png rename to pyaedt/workflows/maxwell3d/images/large/magnet_segmentation.png diff --git a/pyaedt/workflows/maxwell3d/toolkits_catalog.toml b/pyaedt/workflows/maxwell3d/toolkits_catalog.toml new file mode 100644 index 00000000000..fcb55ce22f9 --- /dev/null +++ b/pyaedt/workflows/maxwell3d/toolkits_catalog.toml @@ -0,0 +1,6 @@ +[MagnetSegmentationWizard] +name = "Magnet Segmentation Wizard" +script = "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py" +icon = "magnet_segmentation.png" +template = "Run_PyAEDT_Toolkit_Script" +pip = "ansys-magnet-segmentation-toolkit" diff --git a/pyaedt/misc/images/large/logo.png b/pyaedt/workflows/project/images/large/logo.png similarity index 100% rename from pyaedt/misc/images/large/logo.png rename to pyaedt/workflows/project/images/large/logo.png diff --git a/pyaedt/workflows/project/toolkit_manager.py b/pyaedt/workflows/project/toolkit_manager.py index 3b0382172f5..a4dd7669497 100644 --- a/pyaedt/workflows/project/toolkit_manager.py +++ b/pyaedt/workflows/project/toolkit_manager.py @@ -29,7 +29,10 @@ from pyaedt import Desktop from pyaedt import is_windows -from pyaedt.misc.install_extra_toolkits import available_toolkits +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): @@ -53,11 +56,15 @@ package_dir = os.path.join(venv_dir, "Lib", "site-packages") -def create_toolkit_page(frame, open_source_toolkits): +def create_toolkit_page(frame, window_name, internal_toolkits): """Create page to display toolkit on.""" # Available toolkits - toolkits = ["Custom"] + open_source_toolkits - max_length = max(len(item) for item in toolkits) + if window_name == "Project": + # Remove PyAEDT installer toolkits from the list + internal_toolkits = internal_toolkits[:-4] + 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") @@ -101,7 +108,7 @@ def update_page(event=None): install_button.config(text="Install") uninstall_button.config(state="normal") else: - if is_toolkit_installed(selected_toolkit): + if is_toolkit_installed(selected_toolkit, window_name): install_button.config(text="Update") uninstall_button.config(state="normal") else: @@ -131,21 +138,22 @@ def update_page(event=None): return install_button, uninstall_button, input_file, toolkits_combo, toolkit_name -def is_toolkit_installed(toolkit_name): +def is_toolkit_installed(toolkit_name, window_name): """Check if toolkit is installed.""" if toolkit_name == "Custom": return False - script_file = os.path.normpath(os.path.join(package_dir, available_toolkits[toolkit_name]["toolkit_script"])) + toolkits = available_toolkits() + script_file = os.path.normpath(os.path.join(package_dir, toolkits[window_name][toolkit_name]["script"])) return True if os.path.isfile(script_file) else False -def open_window(window, window_name, open_source_toolkits): +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, open_source_toolkits + window, window_name, internal_toolkits ) root.minsize(500, 250) return install_button, uninstall_button, input_file, toolkits_combo, toolkit_name @@ -164,14 +172,18 @@ def __get_command_function( def toolkit_window(toolkit_level="Project"): """Create interactive toolkit window.""" toolkit_window_var = tk.Toplevel(root) - open_source_toolkits = [] - for toolkit_name, toolkit_info in available_toolkits.items(): - if toolkit_info["installation_path"].lower() == toolkit_level.lower(): - open_source_toolkits.append(toolkit_name) + + 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_button, uninstall_button, input_file, toolkits_combo, toolkit_name = open_window( - toolkit_window_var, toolkit_level, open_source_toolkits - ) install_command = __get_command_function( True, toolkit_level, input_file, toolkits_combo, toolkit_name, install_button, uninstall_button @@ -205,20 +217,20 @@ def button_is_clicked( if selected_toolkit_name != "Custom": desktop.logger.info("TEST: VENV {}".format(venv_dir)) - if is_toolkit_installed(selected_toolkit_name) and install_action: + if is_toolkit_installed(selected_toolkit_name, toolkit_level) and install_action: desktop.logger.info("Updating {}".format(selected_toolkit_name)) - desktop.add_custom_toolkit(selected_toolkit_name, file) + add_custom_toolkit(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)) - desktop.add_custom_toolkit(selected_toolkit_name, file) + add_custom_toolkit(selected_toolkit_name, file) install_button.config(text="Update") uninstall_button.config(state="normal") - elif is_toolkit_installed(selected_toolkit_name) and not install_action: + elif is_toolkit_installed(selected_toolkit_name, toolkit_level) and not install_action: desktop.logger.info("Uninstalling {}".format(selected_toolkit_name)) - desktop.add_custom_toolkit(selected_toolkit_name, install=False) + add_custom_toolkit(selected_toolkit_name, install=False) install_button.config(text="Install") uninstall_button.config(state="disabled") desktop.logger.info("{} uninstalled".format(selected_toolkit_name)) @@ -235,9 +247,10 @@ def button_is_clicked( 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): - desktop.add_script_to_menu( - toolkit_name=name, - script_path=file, + add_script_to_menu( + desktop_object=desktop, + name=name, + script_file=file, product=toolkit_level, executable_interpreter=executable_interpreter, ) @@ -245,7 +258,7 @@ def button_is_clicked( desktop.logger.info("PyAEDT environment is not installed.") else: desktop.logger.info("Uninstall {}.".format(name)) - desktop.remove_script_from_menu(name, product=toolkit_level) + remove_script_from_menu(desktop_object=desktop, toolkit_name=name, product=toolkit_level) desktop.odesktop.CloseAllWindows() desktop.odesktop.RefreshToolkitUI() @@ -256,7 +269,7 @@ def button_is_clicked( root.title("AEDT Toolkit Manager") # Load the logo for the main window -icon_path = os.path.join(os.path.dirname(__file__), "../../misc/images", "large", "logo.png") +icon_path = os.path.join(os.path.dirname(__file__), "images", "large", "logo.png") im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) From 67f91760c2d33bcb075b8bde54783882e39c0a5b Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 3 May 2024 08:49:23 +0200 Subject: [PATCH 31/44] Install custom toolkit --- .../Resources/PyAEDTInstallerFromDesktop.py | 3 +- pyaedt/workflows/customize_automation_tab.py | 44 ++++++++++++------- pyaedt/workflows/hfss/toolkits_catalog.toml | 3 +- pyaedt/workflows/project/toolkit_manager.py | 13 +++--- .../templates/Run_Toolkit_Manager.py_build | 6 ++- 5 files changed, 43 insertions(+), 26 deletions(-) diff --git a/doc/source/Resources/PyAEDTInstallerFromDesktop.py b/doc/source/Resources/PyAEDTInstallerFromDesktop.py index a38df11744b..6e181d107a7 100644 --- a/doc/source/Resources/PyAEDTInstallerFromDesktop.py +++ b/doc/source/Resources/PyAEDTInstallerFromDesktop.py @@ -156,7 +156,8 @@ 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 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)) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index fa3bc01aa0b..61efdbb93fc 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -22,6 +22,7 @@ import os import shutil +import subprocess import sys from xml.dom.minidom import parseString import xml.etree.ElementTree as ET @@ -29,6 +30,7 @@ from pyaedt import is_linux from pyaedt.generic.general_methods import read_toml +import pyaedt.workflows import pyaedt.workflows.templates """Methods to add new automation tabs in AEDT.""" @@ -357,15 +359,25 @@ def add_custom_toolkit(desktop_object, toolkit_name, wheel_toolkit=None, install ------- bool """ - toolkit = available_toolkits[toolkit_name] + toolkits = available_toolkits() + toolkit_info = None + product_name = None + for product in toolkits.keys(): + 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 self.aedt_version_id > "2023.1" else "3.7" + python_version = "3.10" if desktop_object.aedt_version_id > "2023.1" else "3.7" - if is_windows: + if not is_linux: base_venv = os.path.normpath( os.path.join( - self.install_path, + desktop_object.install_path, "commonfiles", "CPython", python_version.replace(".", "_"), @@ -378,7 +390,7 @@ def add_custom_toolkit(desktop_object, toolkit_name, wheel_toolkit=None, install else: base_venv = os.path.normpath( os.path.join( - self.install_path, + desktop_object.install_path, "commonfiles", "CPython", python_version.replace(".", "_"), @@ -389,8 +401,6 @@ def add_custom_toolkit(desktop_object, toolkit_name, wheel_toolkit=None, install ) ) - desktop_object.logger.info(base_venv) - def run_command(command): try: if is_linux: # pragma: no cover @@ -406,9 +416,9 @@ def run_command(command): print("Exception occurred:", str(e)) return 1 # Return non-zero exit code for indicating an error - version = self.odesktop.GetVersion()[2:6].replace(".", "") + version = desktop_object.odesktop.GetVersion()[2:6].replace(".", "") - if is_windows: + 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") @@ -438,12 +448,12 @@ def run_command(command): is_installed = False script_file = None - if os.path.isdir(os.path.normpath(os.path.join(package_dir, toolkit["toolkit_script"]))): - script_file = os.path.normpath(os.path.join(package_dir, toolkit["toolkit_script"])) + 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["toolkit_script"])) + script_file = os.path.normpath(os.path.join(dirpath, "site-packages", toolkit_info["script"])) break if os.path.isfile(script_file): is_installed = True @@ -464,7 +474,7 @@ def run_command(command): with zipfile.ZipFile(wheel_toolkit, "r") as zip_ref: zip_ref.extractall(unzipped_path) - package_name = available_toolkits[toolkit_name]["package_name"] + package_name = toolkit_info["package_name"] run_command( '"{}" install --no-cache-dir --no-index --find-links={} {}'.format(pip_exe, unzipped_path, package_name) ) @@ -473,7 +483,7 @@ def run_command(command): run_command('"{}" --default-timeout=1000 install {}'.format(pip_exe, toolkit["pip"])) elif not install and is_installed: # Uninstall toolkit - run_command('"{}" --default-timeout=1000 uninstall -y {}'.format(pip_exe, toolkit["package_name"])) + run_command('"{}" --default-timeout=1000 uninstall -y {}'.format(pip_exe, toolkit_info["package_name"])) elif install and is_installed: # Update toolkit run_command('"{}" --default-timeout=1000 install {} -U'.format(pip_exe, toolkit["pip"])) @@ -483,7 +493,7 @@ def run_command(command): toolkit_dir = os.path.join(desktop_object.personallib, "Toolkits") tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) - script_image = os.path.join(os.path.dirname(__file__), "misc", "images", "large", toolkit["image"]) + script_image = os.path.join(os.path.dirname(pyaedt.workflows.__file__), "images", "large", toolkit["image"]) if install: if not os.path.exists(tool_dir): @@ -499,12 +509,12 @@ def run_command(command): executable_interpreter=python_exe, ) else: - toolkit_dir = os.path.join(self.personallib, "Toolkits") + toolkit_dir = os.path.join(desktop_object.personallib, "Toolkits") tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) if os.path.exists(tool_dir): # Install toolkit inside AEDT - self.remove_script_from_menu( + remove_script_from_menu( toolkit_name=toolkit_name, product=toolkit["installation_path"], ) diff --git a/pyaedt/workflows/hfss/toolkits_catalog.toml b/pyaedt/workflows/hfss/toolkits_catalog.toml index 8b1360e69ab..8ce1a1abe33 100644 --- a/pyaedt/workflows/hfss/toolkits_catalog.toml +++ b/pyaedt/workflows/hfss/toolkits_catalog.toml @@ -1,6 +1,7 @@ [AntennaWizard] name = "Antenna Wizard" script = "ansys/aedt/toolkits/antenna/run_toolkit.py" -icon = "antenna.png" +icon = "images/large/antenna.png" template = "Run_PyAEDT_Toolkit_Script" pip = "git+https://github.com/ansys/pyaedt-antenna-toolkit.git" +package_name = "ansys.aedt.toolkits.antenna" diff --git a/pyaedt/workflows/project/toolkit_manager.py b/pyaedt/workflows/project/toolkit_manager.py index a4dd7669497..12e8612a538 100644 --- a/pyaedt/workflows/project/toolkit_manager.py +++ b/pyaedt/workflows/project/toolkit_manager.py @@ -33,6 +33,7 @@ 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 +import pyaedt.workflows.project env_vars = ["PYAEDT_SCRIPT_VERSION", "PYAEDT_SCRIPT_PORT", "PYAEDT_STUDENT_VERSION"] if all(var in os.environ for var in env_vars): @@ -41,7 +42,7 @@ port = int(os.environ["PYAEDT_SCRIPT_PORT"]) student_version = False if os.environ["PYAEDT_STUDENT_VERSION"] == "False" else True else: - version = "2024.1" + version = "24.1" port = 0 student_version = False @@ -216,21 +217,20 @@ def button_is_clicked( desktop.odesktop.CloseAllWindows() if selected_toolkit_name != "Custom": - desktop.logger.info("TEST: VENV {}".format(venv_dir)) if is_toolkit_installed(selected_toolkit_name, toolkit_level) and install_action: desktop.logger.info("Updating {}".format(selected_toolkit_name)) - add_custom_toolkit(selected_toolkit_name, file) + 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(selected_toolkit_name, file) + 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(selected_toolkit_name, install=False) + 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)) @@ -246,6 +246,7 @@ def button_is_clicked( 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, @@ -269,7 +270,7 @@ def button_is_clicked( root.title("AEDT Toolkit Manager") # Load the logo for the main window -icon_path = os.path.join(os.path.dirname(__file__), "images", "large", "logo.png") +icon_path = os.path.join(os.path.dirname(pyaedt.workflows.project.__file__), "images", "large", "logo.png") im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) diff --git a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build index 8002154e5d3..6a1a51e2dd0 100644 --- a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build +++ b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build @@ -33,6 +33,8 @@ def main(): try: # 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) @@ -40,7 +42,9 @@ def main(): version = str(oDesktop.GetVersion()[:6]) os.environ["PYAEDT_SCRIPT_VERSION"] = version if "Ansys Student" in str(oDesktop.GetExeDir()): - os.environ["PYAEDT_STUDENT_VERSION"] = True + 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: From fda0be7df88b889717e5edf786e68bfac816774d Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 3 May 2024 10:51:03 +0200 Subject: [PATCH 32/44] Install toolkit with pip --- pyaedt/workflows/customize_automation_tab.py | 28 +++++++++---------- pyaedt/workflows/hfss/toolkits_catalog.toml | 2 +- .../workflows/maxwell3d/toolkits_catalog.toml | 3 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index 61efdbb93fc..36c2057686b 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -463,7 +463,7 @@ def run_command(command): 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["pip"])) + run_command('"{}" uninstall --yes {}'.format(pip_exe, toolkit_info["pip"])) import zipfile unzipped_path = os.path.join( @@ -474,49 +474,49 @@ def run_command(command): with zipfile.ZipFile(wheel_toolkit, "r") as zip_ref: zip_ref.extractall(unzipped_path) - package_name = toolkit_info["package_name"] + 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["pip"])) + 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_name"])) + 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["pip"])) + 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, toolkit["installation_path"], toolkit_name) + tool_dir = os.path.join(toolkit_dir, product_name, toolkit_info["name"]) - script_image = os.path.join(os.path.dirname(pyaedt.workflows.__file__), "images", "large", toolkit["image"]) + 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_name, + name=toolkit_info["name"], script_file=script_file, icon_file=script_image, - product=toolkit["installation_path"], + product=product_name, template_file="Run_PyAEDT_Toolkit_Script", - copy_to_personal_lib=False, + copy_to_personal_lib=True, executable_interpreter=python_exe, ) else: - toolkit_dir = os.path.join(desktop_object.personallib, "Toolkits") - tool_dir = os.path.join(toolkit_dir, toolkit["installation_path"], toolkit_name) - if os.path.exists(tool_dir): # Install toolkit inside AEDT remove_script_from_menu( + desktop_object=desktop_object, toolkit_name=toolkit_name, - product=toolkit["installation_path"], + product=product_name, ) diff --git a/pyaedt/workflows/hfss/toolkits_catalog.toml b/pyaedt/workflows/hfss/toolkits_catalog.toml index 8ce1a1abe33..8626340b3fa 100644 --- a/pyaedt/workflows/hfss/toolkits_catalog.toml +++ b/pyaedt/workflows/hfss/toolkits_catalog.toml @@ -4,4 +4,4 @@ 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_name = "ansys.aedt.toolkits.antenna" +package = "ansys.aedt.toolkits.antenna" diff --git a/pyaedt/workflows/maxwell3d/toolkits_catalog.toml b/pyaedt/workflows/maxwell3d/toolkits_catalog.toml index fcb55ce22f9..1b403d6e8df 100644 --- a/pyaedt/workflows/maxwell3d/toolkits_catalog.toml +++ b/pyaedt/workflows/maxwell3d/toolkits_catalog.toml @@ -1,6 +1,7 @@ [MagnetSegmentationWizard] name = "Magnet Segmentation Wizard" script = "ansys/aedt/toolkits/magnet_segmentation/run_toolkit.py" -icon = "magnet_segmentation.png" +icon = "images/large/magnet_segmentation.png" template = "Run_PyAEDT_Toolkit_Script" pip = "ansys-magnet-segmentation-toolkit" +package = "ansys-magnet-segmentation-toolkit" From 890a14c46d4a568cc8e0e853f9cc338aabf86f5e Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 3 May 2024 11:03:47 +0200 Subject: [PATCH 33/44] Fix UT --- _unittest/test_01_Design.py | 12 +++++++----- pyaedt/workflows/customize_automation_tab.py | 12 ++++++------ pyaedt/workflows/project/toolkit_manager.py | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/_unittest/test_01_Design.py b/_unittest/test_01_Design.py index 3cba663e92b..981c07b6af7 100644 --- a/_unittest/test_01_Design.py +++ b/_unittest/test_01_Design.py @@ -14,6 +14,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": @@ -397,17 +398,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/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index 36c2057686b..c3e0f4bfe10 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -515,19 +515,19 @@ def run_command(command): # Install toolkit inside AEDT remove_script_from_menu( desktop_object=desktop_object, - toolkit_name=toolkit_name, + name=toolkit_info["name"], product=product_name, ) -def remove_script_from_menu(desktop_object, toolkit_name, product="Project"): +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. - toolkit_name : str + name : str Name of the toolkit to remove. product : str, optional Product to which the toolkit applies. The default is ``"Project"``, in which case @@ -540,11 +540,11 @@ def remove_script_from_menu(desktop_object, toolkit_name, product="Project"): toolkit_dir = os.path.join(desktop_object.personallib, "Toolkits") aedt_version = desktop_object.aedt_version_id - tool_dir = os.path.join(toolkit_dir, product, toolkit_name) + 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), toolkit_name) - desktop_object.logger.info("{} toolkit removed successfully.".format(toolkit_name)) + remove_xml_tab(os.path.join(toolkit_dir, product), name) + desktop_object.logger.info("{} toolkit removed successfully.".format(name)) return True diff --git a/pyaedt/workflows/project/toolkit_manager.py b/pyaedt/workflows/project/toolkit_manager.py index 12e8612a538..5b21b357f53 100644 --- a/pyaedt/workflows/project/toolkit_manager.py +++ b/pyaedt/workflows/project/toolkit_manager.py @@ -259,7 +259,7 @@ def button_is_clicked( desktop.logger.info("PyAEDT environment is not installed.") else: desktop.logger.info("Uninstall {}.".format(name)) - remove_script_from_menu(desktop_object=desktop, toolkit_name=name, product=toolkit_level) + remove_script_from_menu(desktop_object=desktop, name=name, product=toolkit_level) desktop.odesktop.CloseAllWindows() desktop.odesktop.RefreshToolkitUI() From 85531727359745e200fccf6f77ccec4c08f9bc4d Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 3 May 2024 11:21:59 +0200 Subject: [PATCH 34/44] Fix Codacy --- pyaedt/workflows/customize_automation_tab.py | 27 +++++++++++++------- pyaedt/workflows/project/pyaedt_installer.py | 4 +-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index c3e0f4bfe10..bd1506ab6df 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -22,19 +22,24 @@ import os import shutil -import subprocess +import subprocess # nosec import sys + +import defusedxml.ElementTree as ET +import defusedxml.minidom + +defusedxml.defuse_stdlib() + +import warnings from xml.dom.minidom import parseString -import xml.etree.ElementTree as ET -from xml.etree.ElementTree import ParseError + +from defusedxml.ElementTree import ParseError from pyaedt import is_linux from pyaedt.generic.general_methods import read_toml import pyaedt.workflows import pyaedt.workflows.templates -"""Methods to add new automation tabs in AEDT.""" - def add_automation_tab( name, lib_dir, icon_file=None, product="Project", template="Run PyAEDT Toolkit Script", overwrite=False @@ -156,7 +161,7 @@ def remove_automation_tab(name, lib_dir): 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"] == toolkitname][0] + b = [button for button in buttons if button.attrib["label"] == name][0] panel.remove(b) create_xml_tab(root, tab_config_file_path) @@ -172,7 +177,11 @@ def create_xml_tab(root, output_file): output_file : str Full name of the file to save the XML tab. """ - + lines = [ + line + for line in defusedxml.minidom.parseString(ET.tostring(root)).toprettyxml(indent=" " * 4).split("\n") + if line.strip() + ] lines = [line for line in parseString(ET.tostring(root)).toprettyxml(indent=" " * 4).split("\n") if line.strip()] xml_str = "\n".join(lines) @@ -293,7 +302,7 @@ def add_script_to_menu( 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) + 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) @@ -362,7 +371,7 @@ def add_custom_toolkit(desktop_object, toolkit_name, wheel_toolkit=None, install toolkits = available_toolkits() toolkit_info = None product_name = None - for product in toolkits.keys(): + for product in toolkits: if toolkit_name in toolkits[product]: toolkit_info = toolkits[product][toolkit_name] product_name = product diff --git a/pyaedt/workflows/project/pyaedt_installer.py b/pyaedt/workflows/project/pyaedt_installer.py index 38e2bdadd2d..b464993d032 100644 --- a/pyaedt/workflows/project/pyaedt_installer.py +++ b/pyaedt/workflows/project/pyaedt_installer.py @@ -90,8 +90,8 @@ def add_pyaedt_to_aedt( if pid and new_desktop_session: try: os.kill(pid, 9) - except Exception: - pass + except Exception: # pragma: no cover + return False def __add_pyaedt_tabs(desktop_object): From e429f04f4d87b26fb92489ee46bf20d0593d0bfe Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 3 May 2024 11:30:31 +0200 Subject: [PATCH 35/44] Fix Codacy --- _unittest/test_01_toolkit_icons.py | 7 ++++++- pyaedt/workflows/customize_automation_tab.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/_unittest/test_01_toolkit_icons.py b/_unittest/test_01_toolkit_icons.py index 272f3e398c0..6bf3ab4ac6f 100644 --- a/_unittest/test_01_toolkit_icons.py +++ b/_unittest/test_01_toolkit_icons.py @@ -1,5 +1,10 @@ import os -import xml.etree.ElementTree as ET + +import defusedxml.ElementTree as ET +import defusedxml.minidom + +defusedxml.defuse_stdlib() + import pytest diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index bd1506ab6df..10c579fe2d2 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -24,16 +24,16 @@ import shutil import subprocess # nosec import sys +import xml.etree.ElementTree as ET # nosec -import defusedxml.ElementTree as ET import defusedxml.minidom defusedxml.defuse_stdlib() import warnings -from xml.dom.minidom import parseString from defusedxml.ElementTree import ParseError +from defusedxml.minidom import parseString from pyaedt import is_linux from pyaedt.generic.general_methods import read_toml From 40ed676518471a13ebb693f79ea1ad8efd7892bc Mon Sep 17 00:00:00 2001 From: maxcapodi78 Date: Fri, 3 May 2024 11:34:02 +0200 Subject: [PATCH 36/44] Added examples to Workflows --- pyaedt/desktop.py | 3 +- pyaedt/generic/general_methods.py | 10 +- pyaedt/modeler/modeler3d.py | 127 +++++++++--------- pyaedt/workflows/hfss3dlayout/export_to_3D.py | 88 ++++++++++++ .../hfss3dlayout/toolkits_catalog.toml | 6 + pyaedt/workflows/project/create_report.py | 39 ++++++ 6 files changed, 201 insertions(+), 72 deletions(-) create mode 100644 pyaedt/workflows/hfss3dlayout/export_to_3D.py create mode 100644 pyaedt/workflows/hfss3dlayout/toolkits_catalog.toml create mode 100644 pyaedt/workflows/project/create_report.py diff --git a/pyaedt/desktop.py b/pyaedt/desktop.py index de7016b62f4..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) 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/modeler/modeler3d.py b/pyaedt/modeler/modeler3d.py index 590a94e0420..58d78dbeb05 100644 --- a/pyaedt/modeler/modeler3d.py +++ b/pyaedt/modeler/modeler3d.py @@ -897,7 +897,7 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import ------- List of :class:`pyaedt.modeler.Object3d.Object3d` """ - nas_to_dict = {"Points": {}, "PointsId": {}, "Triangles": {}, "Lines": {}, "Solids": {}} + nas_to_dict = {"Points": {}, "PointsId": {}, "Triangles": [], "Lines": {}, "Solids": {}} self.logger.reset_timer() self.logger.info("Loading file") @@ -927,22 +927,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 +944,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,22 +954,11 @@ 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]) @@ -1005,6 +983,30 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import 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 ["CTETRA*"]: + obj_id = int(line[8:24]) + n = [] + + 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)]) + + # 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]] + + from itertools import combinations + + for k in list(combinations(n, 3)): + tri = [int(k[0]), int(k[1]), int(k[2])] + tri.sort() + if tri not in nas_to_dict["Triangles"]: + nas_to_dict["Triangles"].append(tri) + elif line_type in ["CROD", "CBEAM"]: obj_id = int(line[16:24]) n1 = int(line[24:32]) @@ -1021,35 +1023,34 @@ 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"]: + 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") f.write("endsolid\n") f.close() diff --git a/pyaedt/workflows/hfss3dlayout/export_to_3D.py b/pyaedt/workflows/hfss3dlayout/export_to_3D.py new file mode 100644 index 00000000000..df1e836f0bd --- /dev/null +++ b/pyaedt/workflows/hfss3dlayout/export_to_3D.py @@ -0,0 +1,88 @@ +import os +from tkinter import Button +from tkinter import Entry +from tkinter import Label +from tkinter import RAISED +from tkinter import StringVar +from tkinter import Tk +from tkinter import mainloop + +from pyaedt import Desktop +from pyaedt import Hfss +from pyaedt import Hfss3dLayout +from pyaedt import Icepak +from pyaedt import Maxwell3d +from pyaedt import Q3d + +master = Tk() +var = StringVar() +label = Label(master, textvariable=var, relief=RAISED) +var.set("1 - Export to HFSS\n2 - Export to Q3D\n3 - Export to Maxwell 3D\n4 - Export to Icepak") +label.pack() +e = Entry(master) +e.pack() + +e.focus_set() +choice = "1" + + +def callback(): + global choice + choice = e.get() # This is the text you may want to use later + master.destroy() + return True + + +b = Button(master, text="OK", width=10, command=callback) +b.pack() + +mainloop() +if choice not in ["1", "2", "3", "4"]: + raise Exception("Wrong input.") +suffixes = {"1": "HFSS", "2": "Q3D", "3": "M3D", "4": "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 == "2": + 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 == "2": + 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 == "3": + app2 = Maxwell3d(projectname=app.project_name) + elif choice == "4": + 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/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/project/create_report.py b/pyaedt/workflows/project/create_report.py new file mode 100644 index 00000000000..55a0ad348cd --- /dev/null +++ b/pyaedt/workflows/project/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}") From b30ad555281926ddbc0bc6b3af62e8cdd76b9c70 Mon Sep 17 00:00:00 2001 From: maxcapodi78 Date: Fri, 3 May 2024 11:49:32 +0200 Subject: [PATCH 37/44] Added examples to Workflows --- .../hfss3dlayout/images/large/cad3d.png | Bin 0 -> 2447 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pyaedt/workflows/hfss3dlayout/images/large/cad3d.png diff --git a/pyaedt/workflows/hfss3dlayout/images/large/cad3d.png b/pyaedt/workflows/hfss3dlayout/images/large/cad3d.png new file mode 100644 index 0000000000000000000000000000000000000000..13e423090603b085bf31b25a7a842b5628cb679c GIT binary patch literal 2447 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 Date: Fri, 3 May 2024 12:16:18 +0200 Subject: [PATCH 38/44] Fix Codacy --- _unittest/test_01_toolkit_icons.py | 29 +++++++------------- pyaedt/workflows/customize_automation_tab.py | 22 ++++++++------- pyaedt/workflows/project/toolkit_manager.py | 2 +- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/_unittest/test_01_toolkit_icons.py b/_unittest/test_01_toolkit_icons.py index 6bf3ab4ac6f..55ec3a74cbd 100644 --- a/_unittest/test_01_toolkit_icons.py +++ b/_unittest/test_01_toolkit_icons.py @@ -8,7 +8,7 @@ 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) @@ -22,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] @@ -34,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( """ @@ -52,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] @@ -60,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( """ @@ -77,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( """ @@ -93,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] @@ -111,13 +110,5 @@ def validate_file_exists_and_pyaedt_tabs_added(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/console.png", - "images/gallery/run_script.png", - "images/gallery/toolkit_manager.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/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index 10c579fe2d2..19372be23df 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -62,6 +62,11 @@ def add_automation_tab( Whether to overwrite the existing automation tab. The default is ``False``, in which case is adding new tabs to the existing ones. + Returns + ------- + str + Automation tab path. + """ tab_config_file_path = os.path.join(lib_dir, product, "TabConfig.xml") @@ -69,7 +74,7 @@ def add_automation_tab( root = ET.Element("TabConfig") else: try: - tree = ET.parse(tab_config_file_path) + 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 @@ -94,6 +99,9 @@ def add_automation_tab( b = [button for button in buttons if button.attrib["label"] == name][0] panel.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)) @@ -116,6 +124,7 @@ def add_automation_tab( 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): @@ -139,7 +148,7 @@ def remove_automation_tab(name, lib_dir): if not os.path.isfile(tab_config_file_path): return True try: - tree = ET.parse(tab_config_file_path) + 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 @@ -177,11 +186,6 @@ def create_xml_tab(root, output_file): output_file : str Full name of the file to save the XML tab. """ - lines = [ - line - for line in defusedxml.minidom.parseString(ET.tostring(root)).toprettyxml(indent=" " * 4).split("\n") - if line.strip() - ] lines = [line for line in parseString(ET.tostring(root)).toprettyxml(indent=" " * 4).split("\n") if line.strip()] xml_str = "\n".join(lines) @@ -195,7 +199,7 @@ def remove_xml_tab(toolkit_dir, name): if not os.path.isfile(tab_config_file_path): return True try: - tree = ET.parse(tab_config_file_path) + 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 @@ -343,8 +347,6 @@ def add_script_to_menu( out_file.write(build_file_data) if aedt_version >= "2023.2": - if not icon_file: - icon_file = os.path.join(os.path.dirname(__file__), "images", "large", "pyansys.png") add_automation_tab(name, toolkit_dir, icon_file=icon_file, product=product, template=file_name_dest) desktop_object.logger.info("{} installed".format(name)) return True diff --git a/pyaedt/workflows/project/toolkit_manager.py b/pyaedt/workflows/project/toolkit_manager.py index 5b21b357f53..3fa7d07da15 100644 --- a/pyaedt/workflows/project/toolkit_manager.py +++ b/pyaedt/workflows/project/toolkit_manager.py @@ -42,7 +42,7 @@ port = int(os.environ["PYAEDT_SCRIPT_PORT"]) student_version = False if os.environ["PYAEDT_STUDENT_VERSION"] == "False" else True else: - version = "24.1" + version = "241" port = 0 student_version = False From 9e3a1f9458b98895b97d9b2948b898d132afe083 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 3 May 2024 13:09:21 +0200 Subject: [PATCH 39/44] Separate installer and project --- .../Resources/PyAEDTInstallerFromDesktop.py | 2 +- pyaedt/workflows/customize_automation_tab.py | 67 +++++++++++------- pyaedt/workflows/installer/__init__.py | 21 ++++++ .../{project => installer}/console_setup.py | 0 .../{project => installer}/create_report.py | 0 .../images/large/console.png | Bin .../images/large/jupyter.png | Bin .../images/large/logo.png | Bin .../images/large/run_script.png | Bin .../images/large/toolkit_manager.png | Bin .../jupyter_template.ipynb | 0 .../pyaedt_installer.py | 1 + .../{project => installer}/toolkit_manager.py | 7 +- .../workflows/installer/toolkits_catalog.toml | 23 ++++++ .../workflows/project/toolkits_catalog.toml | 23 ------ 15 files changed, 89 insertions(+), 55 deletions(-) create mode 100644 pyaedt/workflows/installer/__init__.py rename pyaedt/workflows/{project => installer}/console_setup.py (100%) rename pyaedt/workflows/{project => installer}/create_report.py (100%) rename pyaedt/workflows/{project => installer}/images/large/console.png (100%) rename pyaedt/workflows/{project => installer}/images/large/jupyter.png (100%) rename pyaedt/workflows/{project => installer}/images/large/logo.png (100%) rename pyaedt/workflows/{project => installer}/images/large/run_script.png (100%) rename pyaedt/workflows/{project => installer}/images/large/toolkit_manager.png (100%) rename pyaedt/workflows/{project => installer}/jupyter_template.ipynb (100%) rename pyaedt/workflows/{project => installer}/pyaedt_installer.py (98%) rename pyaedt/workflows/{project => installer}/toolkit_manager.py (97%) create mode 100644 pyaedt/workflows/installer/toolkits_catalog.toml diff --git a/doc/source/Resources/PyAEDTInstallerFromDesktop.py b/doc/source/Resources/PyAEDTInstallerFromDesktop.py index 6e181d107a7..7d857c83b6c 100644 --- a/doc/source/Resources/PyAEDTInstallerFromDesktop.py +++ b/doc/source/Resources/PyAEDTInstallerFromDesktop.py @@ -60,7 +60,7 @@ 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.workflows.project.pyaedt_installer 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="{}", student_version={}, new_desktop_session=False)\n'.format( oDesktop.GetVersion()[:6], is_student_version(oDesktop))) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index 19372be23df..0a036a54cf2 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -42,7 +42,13 @@ def add_automation_tab( - name, lib_dir, icon_file=None, product="Project", template="Run PyAEDT Toolkit Script", overwrite=False + 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. @@ -61,6 +67,8 @@ def add_automation_tab( 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 ------- @@ -82,22 +90,22 @@ def add_automation_tab( panels = root.findall("./panel") if panels: - panel_names = [panel.attrib["label"] for panel in panels] - if "Panel_PyAEDT_Toolkits" in panel_names: + 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 = [panel for panel in panels if panel.attrib["label"] == "Panel_PyAEDT_Toolkits"][0] + panel_element = [panel_element for panel_element in panels if panel_element.attrib["label"] == panel][0] else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") + panel_element = ET.SubElement(root, "panel", label=panel) else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") + panel_element = ET.SubElement(root, "panel", label=panel) - buttons = panel.findall("./button") + 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.remove(b) + panel_element.remove(b) if not icon_file: icon_file = os.path.join(os.path.dirname(pyaedt.workflows.__file__), "images", "large", "pyansys.png") @@ -111,7 +119,7 @@ def add_automation_tab( shutil.copy(icon_file, dest_file) ET.SubElement( - panel, + panel_element, "button", label=name, isLarge="1", @@ -127,7 +135,7 @@ def add_automation_tab( return tab_config_file_path -def remove_automation_tab(name, lib_dir): +def remove_automation_tab(name, lib_dir, panel="Panel_PyAEDT_Toolkits"): """Remove automation tab in AEDT. Parameters @@ -136,6 +144,8 @@ def remove_automation_tab(name, lib_dir): Toolkit name. lib_dir : str Path to the library directory. + panel : str, optional + Panel name. The default is ``"Panel_PyAEDT_Toolkits"``. Returns ------- @@ -156,22 +166,22 @@ def remove_automation_tab(name, lib_dir): panels = root.findall("./panel") if panels: - panel_names = [panel.attrib["label"] for panel in panels] - if "Panel_PyAEDT_Toolkits" in panel_names: + 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 = [panel for panel in panels if panel.attrib["label"] == "Panel_PyAEDT_Toolkits"][0] + panel_element = [panel_element for panel_element in panels if panel.attrib["label"] == panel][0] else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") + panel_element = ET.SubElement(root, "panel", label=panel) else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") + panel_element = ET.SubElement(root, "panel", label=panel) - buttons = panel.findall("./button") + 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.remove(b) + panel_element.remove(b) create_xml_tab(root, tab_config_file_path) @@ -193,7 +203,7 @@ def create_xml_tab(root, output_file): f.write(xml_str) -def remove_xml_tab(toolkit_dir, name): +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): @@ -207,22 +217,22 @@ def remove_xml_tab(toolkit_dir, name): panels = root.findall("./panel") if panels: - panel_names = [panel.attrib["label"] for panel in panels] - if "Panel_PyAEDT_Toolkits" in panel_names: + 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 = [panel for panel in panels if panel.attrib["label"] == "Panel_PyAEDT_Toolkits"][0] + panel_element = [panel_element for panel_element in panels if panel_element.attrib["label"] == panel][0] else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") + panel_element = ET.SubElement(root, "panel", label=panel) else: - panel = ET.SubElement(root, "panel", label="Panel_PyAEDT_Toolkits") + panel_element = ET.SubElement(root, "panel", label=panel) - buttons = panel.findall("./button") + 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.remove(b) + panel_element.remove(b) create_xml_tab(root, tab_config_file_path) @@ -261,6 +271,7 @@ def add_script_to_menu( product="Project", copy_to_personal_lib=True, executable_interpreter=None, + panel="Panel_PyAEDT_Toolkits", ): """Add a script to the ribbon menu. @@ -289,6 +300,8 @@ def add_script_to_menu( 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 ------- @@ -347,7 +360,9 @@ def add_script_to_menu( 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) + 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 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/workflows/project/console_setup.py b/pyaedt/workflows/installer/console_setup.py similarity index 100% rename from pyaedt/workflows/project/console_setup.py rename to pyaedt/workflows/installer/console_setup.py diff --git a/pyaedt/workflows/project/create_report.py b/pyaedt/workflows/installer/create_report.py similarity index 100% rename from pyaedt/workflows/project/create_report.py rename to pyaedt/workflows/installer/create_report.py diff --git a/pyaedt/workflows/project/images/large/console.png b/pyaedt/workflows/installer/images/large/console.png similarity index 100% rename from pyaedt/workflows/project/images/large/console.png rename to pyaedt/workflows/installer/images/large/console.png diff --git a/pyaedt/workflows/project/images/large/jupyter.png b/pyaedt/workflows/installer/images/large/jupyter.png similarity index 100% rename from pyaedt/workflows/project/images/large/jupyter.png rename to pyaedt/workflows/installer/images/large/jupyter.png diff --git a/pyaedt/workflows/project/images/large/logo.png b/pyaedt/workflows/installer/images/large/logo.png similarity index 100% rename from pyaedt/workflows/project/images/large/logo.png rename to pyaedt/workflows/installer/images/large/logo.png diff --git a/pyaedt/workflows/project/images/large/run_script.png b/pyaedt/workflows/installer/images/large/run_script.png similarity index 100% rename from pyaedt/workflows/project/images/large/run_script.png rename to pyaedt/workflows/installer/images/large/run_script.png diff --git a/pyaedt/workflows/project/images/large/toolkit_manager.png b/pyaedt/workflows/installer/images/large/toolkit_manager.png similarity index 100% rename from pyaedt/workflows/project/images/large/toolkit_manager.png rename to pyaedt/workflows/installer/images/large/toolkit_manager.png diff --git a/pyaedt/workflows/project/jupyter_template.ipynb b/pyaedt/workflows/installer/jupyter_template.ipynb similarity index 100% rename from pyaedt/workflows/project/jupyter_template.ipynb rename to pyaedt/workflows/installer/jupyter_template.ipynb diff --git a/pyaedt/workflows/project/pyaedt_installer.py b/pyaedt/workflows/installer/pyaedt_installer.py similarity index 98% rename from pyaedt/workflows/project/pyaedt_installer.py rename to pyaedt/workflows/installer/pyaedt_installer.py index b464993d032..eae13eeb397 100644 --- a/pyaedt/workflows/project/pyaedt_installer.py +++ b/pyaedt/workflows/installer/pyaedt_installer.py @@ -120,4 +120,5 @@ def __add_pyaedt_tabs(desktop_object): product="Project", copy_to_personal_lib=True, executable_interpreter=None, + panel="Panel_PyAEDT_Installer", ) diff --git a/pyaedt/workflows/project/toolkit_manager.py b/pyaedt/workflows/installer/toolkit_manager.py similarity index 97% rename from pyaedt/workflows/project/toolkit_manager.py rename to pyaedt/workflows/installer/toolkit_manager.py index 3fa7d07da15..f239f1f3f01 100644 --- a/pyaedt/workflows/project/toolkit_manager.py +++ b/pyaedt/workflows/installer/toolkit_manager.py @@ -33,7 +33,7 @@ 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 -import pyaedt.workflows.project +import pyaedt.workflows.installer env_vars = ["PYAEDT_SCRIPT_VERSION", "PYAEDT_SCRIPT_PORT", "PYAEDT_STUDENT_VERSION"] if all(var in os.environ for var in env_vars): @@ -60,9 +60,6 @@ def create_toolkit_page(frame, window_name, internal_toolkits): """Create page to display toolkit on.""" # Available toolkits - if window_name == "Project": - # Remove PyAEDT installer toolkits from the list - internal_toolkits = internal_toolkits[:-4] toolkits = ["Custom"] + internal_toolkits max_length = max(len(item) for item in toolkits) + 1 @@ -270,7 +267,7 @@ def button_is_clicked( root.title("AEDT Toolkit Manager") # Load the logo for the main window -icon_path = os.path.join(os.path.dirname(pyaedt.workflows.project.__file__), "images", "large", "logo.png") +icon_path = os.path.join(os.path.dirname(pyaedt.workflows.installer.__file__), "images", "large", "logo.png") im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) 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/project/toolkits_catalog.toml b/pyaedt/workflows/project/toolkits_catalog.toml index a5baf22c440..e69de29bb2d 100644 --- a/pyaedt/workflows/project/toolkits_catalog.toml +++ b/pyaedt/workflows/project/toolkits_catalog.toml @@ -1,23 +0,0 @@ -[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" From b16a6d070051f732e18ac5413216c3cf153e5543 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 3 May 2024 14:49:05 +0200 Subject: [PATCH 40/44] Add workflows as simple scripts --- pyaedt/workflows/customize_automation_tab.py | 21 +++++++++++-- pyaedt/workflows/installer/toolkit_manager.py | 31 +++++++++++++++++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index 0a036a54cf2..b2f15a335d5 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -77,6 +77,8 @@ def add_automation_tab( """ + 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") @@ -315,7 +317,8 @@ def add_script_to_menu( 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) + 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": @@ -367,6 +370,20 @@ def add_script_to_menu( 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" + 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. @@ -563,7 +580,7 @@ def remove_script_from_menu(desktop_object, name, product="Project"): ------- 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) diff --git a/pyaedt/workflows/installer/toolkit_manager.py b/pyaedt/workflows/installer/toolkit_manager.py index f239f1f3f01..8809aad5f65 100644 --- a/pyaedt/workflows/installer/toolkit_manager.py +++ b/pyaedt/workflows/installer/toolkit_manager.py @@ -102,7 +102,13 @@ def create_toolkit_page(frame, window_name, internal_toolkits): def update_page(event=None): selected_toolkit = toolkits_combo.get() - if selected_toolkit == "Custom": + + 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: @@ -113,7 +119,11 @@ def update_page(event=None): install_button.config(text="Install") uninstall_button.config(state="disabled") - if installation_option_action.get() == "Pip" and selected_toolkit != "Custom": + 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") @@ -124,6 +134,9 @@ def update_page(event=None): 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:") @@ -213,7 +226,18 @@ def button_is_clicked( desktop.odesktop.CloseAllWindows() - if selected_toolkit_name != "Custom": + 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) @@ -250,6 +274,7 @@ def button_is_clicked( name=name, script_file=file, product=toolkit_level, + icon_file=icon, executable_interpreter=executable_interpreter, ) else: From 563bc82bc1c99635ab61ecd8c634ac7e45a9f0a9 Mon Sep 17 00:00:00 2001 From: Samuelopez-ansys Date: Fri, 3 May 2024 15:17:25 +0200 Subject: [PATCH 41/44] Add workflows as simple scripts --- pyaedt/workflows/hfss3dlayout/export_to_3D.py | 57 ++++++++++++------ .../{installer => }/images/large/logo.png | Bin pyaedt/workflows/installer/toolkit_manager.py | 4 +- pyaedt/workflows/project/create_report.py | 39 ++++++++++++ pyaedt/workflows/project/images/large/pdf.png | Bin 0 -> 573 bytes .../workflows/project/toolkits_catalog.toml | 6 ++ 6 files changed, 87 insertions(+), 19 deletions(-) rename pyaedt/workflows/{installer => }/images/large/logo.png (100%) create mode 100644 pyaedt/workflows/project/create_report.py create mode 100644 pyaedt/workflows/project/images/large/pdf.png diff --git a/pyaedt/workflows/hfss3dlayout/export_to_3D.py b/pyaedt/workflows/hfss3dlayout/export_to_3D.py index df1e836f0bd..0ab251c87d7 100644 --- a/pyaedt/workflows/hfss3dlayout/export_to_3D.py +++ b/pyaedt/workflows/hfss3dlayout/export_to_3D.py @@ -1,11 +1,15 @@ import os from tkinter import Button -from tkinter import Entry 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 @@ -13,33 +17,52 @@ 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("1 - Export to HFSS\n2 - Export to Q3D\n3 - Export to Maxwell 3D\n4 - Export to Icepak") -label.pack() -e = Entry(master) -e.pack() +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) -e.focus_set() -choice = "1" +combo.focus_set() +choice = "Export to HFSS" def callback(): global choice - choice = e.get() # This is the text you may want to use later + choice = combo.get() master.destroy() return True -b = Button(master, text="OK", width=10, command=callback) -b.pack() +b = Button(master, text="Export", width=40, command=callback) +b.pack(pady=10) mainloop() -if choice not in ["1", "2", "3", "4"]: - raise Exception("Wrong input.") -suffixes = {"1": "HFSS", "2": "Q3D", "3": "M3D", "4": "IPK"} + +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"] @@ -62,19 +85,19 @@ def callback(): setup = h3d.create_setup() suffix = suffixes[choice] - if choice == "2": + 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 == "2": + 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 == "3": + if choice == "Export to Maxwell 3D": app2 = Maxwell3d(projectname=app.project_name) - elif choice == "4": + elif choice == "Export to Icepak": app2 = Icepak(projectname=app.project_name) if app2: app2.copy_solid_bodies_from( diff --git a/pyaedt/workflows/installer/images/large/logo.png b/pyaedt/workflows/images/large/logo.png similarity index 100% rename from pyaedt/workflows/installer/images/large/logo.png rename to pyaedt/workflows/images/large/logo.png diff --git a/pyaedt/workflows/installer/toolkit_manager.py b/pyaedt/workflows/installer/toolkit_manager.py index 8809aad5f65..be01e8813c1 100644 --- a/pyaedt/workflows/installer/toolkit_manager.py +++ b/pyaedt/workflows/installer/toolkit_manager.py @@ -29,11 +29,11 @@ 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 -import pyaedt.workflows.installer env_vars = ["PYAEDT_SCRIPT_VERSION", "PYAEDT_SCRIPT_PORT", "PYAEDT_STUDENT_VERSION"] if all(var in os.environ for var in env_vars): @@ -292,7 +292,7 @@ def button_is_clicked( root.title("AEDT Toolkit Manager") # Load the logo for the main window -icon_path = os.path.join(os.path.dirname(pyaedt.workflows.installer.__file__), "images", "large", "logo.png") +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) 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/pdf.png b/pyaedt/workflows/project/images/large/pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..d2647a81bbe88258a64513c23823514b4ac51788 GIT binary patch literal 573 zcmV-D0>b@?P)N%CP%^J`i2Z(j3oU-Win^m}adesA@FarK0C z^@e!$hk5plfA)@n_L7D6my7n8jQ5(3_nVLRossvTmG`2T_oSKls-O6)q4=<;`M9w8 zxv=@Wv-!NV`n|RKy|((kw)(@o`o+Kd(aikU()`!c{Mpp}+t&Qx-2CC){p#rc@9qBb z@&5Gk{`mL*`uhL>|LI8#*#H0l1$0tQQvf`;z{#4KLs|d;0MbcBK~y-)ozd40f-n?- z;i4!?wJyYk;>3k}i<|dz}_%Mm}PxHLi!EV7jr#VoR}k(5W_Te z3hcJVrPH_$*N>`VPp3^93`lp=I1@6SDR!-vr@>)$(_GNrE4F2|7+dtp!~yE5uh^_Z zPBdq!JYV-^sc`)3np_a_)EOVRNpz_I;Qr=UKxEHF9|%k#3JARcp Date: Fri, 3 May 2024 15:19:52 +0200 Subject: [PATCH 42/44] Add simplorer --- pyaedt/workflows/customize_automation_tab.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index b2f15a335d5..010939e52b2 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -380,6 +380,8 @@ def __tab_map(product): # pragma: no cover return "2DExtractor" elif product.lower() == "q3d": return "Q3DExtractor" + elif product.lower() == "simplorer": + return "TwinBuilder" else: return product From 6cb453db41ec40154ce88a152730622404234c8b Mon Sep 17 00:00:00 2001 From: samuel Date: Fri, 3 May 2024 16:32:21 +0200 Subject: [PATCH 43/44] Fix Linux issues --- pyaedt/workflows/customize_automation_tab.py | 4 +++- pyaedt/workflows/installer/toolkit_manager.py | 17 ++++++++++++++--- .../templates/Run_Toolkit_Manager.py_build | 3 ++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/pyaedt/workflows/customize_automation_tab.py b/pyaedt/workflows/customize_automation_tab.py index 010939e52b2..5f9eb6dd85e 100644 --- a/pyaedt/workflows/customize_automation_tab.py +++ b/pyaedt/workflows/customize_automation_tab.py @@ -120,12 +120,14 @@ def add_automation_tab( 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=dest_file, + image=relative_image_path, script="{}/{}".format(name, template), ) diff --git a/pyaedt/workflows/installer/toolkit_manager.py b/pyaedt/workflows/installer/toolkit_manager.py index be01e8813c1..6ec606b8228 100644 --- a/pyaedt/workflows/installer/toolkit_manager.py +++ b/pyaedt/workflows/installer/toolkit_manager.py @@ -38,7 +38,6 @@ 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"] - version = version[2:6].replace(".", "") port = int(os.environ["PYAEDT_SCRIPT_PORT"]) student_version = False if os.environ["PYAEDT_STUDENT_VERSION"] == "False" else True else: @@ -54,7 +53,7 @@ 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") + package_dir = os.path.join(venv_dir, "lib", "site-packages") def create_toolkit_page(frame, window_name, internal_toolkits): @@ -155,7 +154,19 @@ def is_toolkit_installed(toolkit_name, window_name): return False toolkits = available_toolkits() script_file = os.path.normpath(os.path.join(package_dir, toolkits[window_name][toolkit_name]["script"])) - return True if os.path.isfile(script_file) else False + 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): diff --git a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build index 6a1a51e2dd0..4bebbdba872 100644 --- a/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build +++ b/pyaedt/workflows/templates/Run_Toolkit_Manager.py_build @@ -31,6 +31,7 @@ else: 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__))) @@ -39,7 +40,6 @@ def main(): check_file(python_exe) check_file(pyaedt_script) os.environ["PYAEDT_SCRIPT_PROCESS_ID"] = str(oDesktop.GetProcessID()) - version = str(oDesktop.GetVersion()[:6]) os.environ["PYAEDT_SCRIPT_VERSION"] = version if "Ansys Student" in str(oDesktop.GetExeDir()): os.environ["PYAEDT_STUDENT_VERSION"] = "True" @@ -48,6 +48,7 @@ def main(): 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 = [ From 2fbbdc78606b105a84949263819aca9e2c9c1770 Mon Sep 17 00:00:00 2001 From: maxcapodi78 Date: Fri, 3 May 2024 17:38:01 +0200 Subject: [PATCH 44/44] Added import_nastran to Workflows --- pyaedt/modeler/modeler3d.py | 172 ++++++++---------- .../workflows/project/images/large/cad3d.png | Bin 0 -> 2447 bytes pyaedt/workflows/project/import_nastran.py | 39 ++++ .../workflows/project/toolkits_catalog.toml | 7 + 4 files changed, 117 insertions(+), 101 deletions(-) create mode 100644 pyaedt/workflows/project/images/large/cad3d.png create mode 100644 pyaedt/workflows/project/import_nastran.py diff --git a/pyaedt/modeler/modeler3d.py b/pyaedt/modeler/modeler3d.py index 58d78dbeb05..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` """ + + 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 @@ -960,52 +989,53 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import 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 - 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]] + 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 = int(line[8:24]) + obj_id = line[8:24].strip() n = [] - - n.append(line[24:40].strip()) + 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)]) - # 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]] - from itertools import combinations + tris = [] for k in list(combinations(n, 3)): tri = [int(k[0]), int(k[1]), int(k[2])] - tri.sort() - if tri not in nas_to_dict["Triangles"]: - nas_to_dict["Triangles"].append(tri) + 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 ["CROD", "CBEAM"]: obj_id = int(line[16:24]) @@ -1024,38 +1054,20 @@ def import_nastran(self, file_path, import_lines=True, lines_thickness=0, import f = open(os.path.join(self._app.working_directory, self._app.design_name + "_test.stl"), "w") f.write("solid PyaedtStl\n") for triangle in nas_to_dict["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") + _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: @@ -1088,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/project/images/large/cad3d.png b/pyaedt/workflows/project/images/large/cad3d.png new file mode 100644 index 0000000000000000000000000000000000000000..13e423090603b085bf31b25a7a842b5628cb679c GIT binary patch literal 2447 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