Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: Toolkit installer #4593

Merged
merged 57 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
f339849
Fix toolkit installer
Samuelopez-ansys Mar 11, 2024
827088b
Add tkinter ui for toolkits manager
Samuelopez-ansys Mar 12, 2024
b1d4ab9
Create virtual environment for new toolkits
Samuelopez-ansys Mar 12, 2024
5eb2719
Create virtual environment for new toolkits
Samuelopez-ansys Mar 12, 2024
68c95d8
Virtual environment pointed to AEDT ribbon
Samuelopez-ansys Mar 13, 2024
da2af27
Uninstall toolkit option
Samuelopez-ansys Mar 14, 2024
0722ce8
Wheelhouse
Samuelopez-ansys Mar 14, 2024
5a44bd8
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys Mar 14, 2024
09145bb
Add button
Samuelopez-ansys Mar 15, 2024
047ab5c
Add button in run script
Samuelopez-ansys Mar 15, 2024
cd0c281
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys Apr 22, 2024
ff83282
Fix toolkit manager
Samuelopez-ansys Apr 23, 2024
80d5f01
Install wheelhouse
Samuelopez-ansys Apr 23, 2024
ca66ccc
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys Apr 23, 2024
35631e4
Fix venv issue
Samuelopez-ansys Apr 26, 2024
3f68e63
Change PyAEDT icons
Samuelopez-ansys Apr 26, 2024
943af54
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys Apr 26, 2024
233fa78
Fix Codacy
Samuelopez-ansys Apr 26, 2024
10fd91c
Fix UT
Samuelopez-ansys Apr 26, 2024
141bf22
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys Apr 26, 2024
1117397
Add PyAEDT Console
Samuelopez-ansys Apr 29, 2024
8d828ef
Add suggestion
Samuelopez-ansys Apr 29, 2024
63b7c9a
Add suggestion
Samuelopez-ansys Apr 29, 2024
0b958c4
Apply suggestions from code review
Samuelopez-ansys Apr 29, 2024
b0353c6
Merge remote-tracking branch 'origin/feat/toolkit_installer' into fea…
Samuelopez-ansys Apr 29, 2024
9a7813d
Merge remote-tracking branch 'origin/main' into feat/toolkit_installer
Samuelopez-ansys Apr 29, 2024
ea5463e
Add TK and TCK env variables in Linux
Samuelopez-ansys Apr 29, 2024
10f381c
Add TK and TCK env variables in Linux
Samuelopez-ansys Apr 29, 2024
e878fb2
Fix Linux logo
Samuelopez-ansys Apr 29, 2024
e881388
Linux compatibility
Samuelopez-ansys Apr 29, 2024
5e82308
Fix Maxwell name
Samuelopez-ansys Apr 29, 2024
2c33572
Workflows directory
Samuelopez-ansys Apr 30, 2024
665fdff
Workflows directory
Samuelopez-ansys Apr 30, 2024
ed834a9
Remove old scripts
Samuelopez-ansys Apr 30, 2024
2d6e830
Remove old scripts
Samuelopez-ansys Apr 30, 2024
dcb6826
Remove toolkit manager env
Samuelopez-ansys Apr 30, 2024
9f3b822
Install custom toolkit
Samuelopez-ansys Apr 30, 2024
61ae42d
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys Apr 30, 2024
67f9176
Install custom toolkit
Samuelopez-ansys May 3, 2024
fda0be7
Install toolkit with pip
Samuelopez-ansys May 3, 2024
890a14c
Fix UT
Samuelopez-ansys May 3, 2024
8553172
Fix Codacy
Samuelopez-ansys May 3, 2024
e429f04
Fix Codacy
Samuelopez-ansys May 3, 2024
baa50a3
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys May 3, 2024
40ed676
Added examples to Workflows
May 3, 2024
b30ad55
Added examples to Workflows
May 3, 2024
9214d78
Fix Codacy
Samuelopez-ansys May 3, 2024
5334c79
Merge remote-tracking branch 'origin/feat/toolkit_installer' into fea…
Samuelopez-ansys May 3, 2024
9e3a1f9
Separate installer and project
Samuelopez-ansys May 3, 2024
b16a6d0
Add workflows as simple scripts
Samuelopez-ansys May 3, 2024
563bc82
Add workflows as simple scripts
Samuelopez-ansys May 3, 2024
a2b7d65
Add simplorer
Samuelopez-ansys May 3, 2024
2ff6af1
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys May 3, 2024
6cb453d
Fix Linux issues
Samuelopez-ansys May 3, 2024
554d9a0
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys May 3, 2024
2fbbdc7
Added import_nastran to Workflows
May 3, 2024
b16e811
Merge branch 'main' into feat/toolkit_installer
Samuelopez-ansys May 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions _unittest/test_01_toolkit_icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,21 @@ def test_04_write_to_existing_file_but_no_panels(self):
panel_names = [panel.attrib["label"] for panel in panels]
assert len(panel_names) == 1

def validate_file_exists_and_pyaedt_tabs_added(self, file_path):
@staticmethod
def validate_file_exists_and_pyaedt_tabs_added(file_path):
assert os.path.isfile(file_path) is True
assert ET.parse(file_path) is not None
tree = ET.parse(file_path)
root = tree.getroot()
panels = root.findall("./panel")
panel_names = [panel.attrib["label"] for panel in panels]
assert "Panel_PyAEDT" in panel_names
files_to_verify = ["images/large/pyansys.png", "images/gallery/PyAEDT.png"]
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
197 changes: 160 additions & 37 deletions pyaedt/desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -1686,13 +1686,17 @@ 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, 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
-------
Expand All @@ -1701,46 +1705,158 @@ def add_custom_toolkit(self, toolkit_name): # pragma: no cover
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])

commands.append([executable, "-m", "pip", "install", "--upgrade", package_path])
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",
)
)

if self.aedt_version_id == "2023.1" and is_windows and "AnsysEM" in sys.base_prefix:
commands.append([executable, "-m", "pip", "uninstall", "--yes", "pywin32"])
self.logger.info(base_venv)

for command in commands:
if is_linux:
p = subprocess.Popen(command)
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:
p = subprocess.Popen(" ".join(command))
p.wait()
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(".", "")

install(toolkit["pip"], toolkit.get("package_name", None))
import site
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", "site-packages")
else:
venv_dir = os.path.join(os.environ["HOME"], "pyaedt_env_ide", "toolkits_v{}".format(version))
python_exe = os.path.join(venv_dir, "bin", "python")
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(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", "")
)

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,
)
# 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 = 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")
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):
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:
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(
Expand All @@ -1750,7 +1866,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.

Expand All @@ -1773,6 +1889,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
-------
Expand All @@ -1782,6 +1900,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")
Expand All @@ -1800,7 +1923,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")
Expand All @@ -1821,7 +1944,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()
Expand Down
2 changes: 1 addition & 1 deletion pyaedt/misc/Run_PyAEDT_Toolkit_Script.py_build
Original file line number Diff line number Diff line change
Expand Up @@ -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))


Expand Down
92 changes: 92 additions & 0 deletions pyaedt/misc/Run_Toolkit_Manager.py_build
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
"""
* * * This script is meant to run in IronPython within the Ansys Electronics Desktop. * * *
Samuelopez-ansys marked this conversation as resolved.
Show resolved Hide resolved
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
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, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
else:
command = [
'"{}"'.format(python_exe),
'"{}"'.format(pyaedt_script),
]
my_env = os.environ.copy()
subprocess.Popen(" ".join(command), env=my_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
except Exception as e:
show_error(str(e))


def check_file(file_path):
if not os.path.isfile(file_path):
show_error('"{}" does not exist. Please click on the "Install PyAEDT" button in the Automation ribbon.'.format(
Samuelopez-ansys marked this conversation as resolved.
Show resolved Hide resolved
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()
Loading
Loading