From 8e0bca8550c567bfeeaf50a0be3f163486a1b847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Morais?= <146729917+SMoraisAnsys@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:02:05 +0100 Subject: [PATCH 1/6] REFACTOR: Improve vulnerability handling in workflows (#5365) --- pyproject.toml | 1 + .../workflows/customize_automation_tab.py | 57 +++++++++++-------- .../core/workflows/project/create_report.py | 11 ++-- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7da3b829daa..19ea72e9b71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ "pytomlpp; python_version < '3.12'", "rpyc>=6.0.0,<6.1", "pyyaml", + "defusedxml>=0.7,<8.0" ] [project.optional-dependencies] diff --git a/src/ansys/aedt/core/workflows/customize_automation_tab.py b/src/ansys/aedt/core/workflows/customize_automation_tab.py index 4a0736ce80a..7261ef29f22 100644 --- a/src/ansys/aedt/core/workflows/customize_automation_tab.py +++ b/src/ansys/aedt/core/workflows/customize_automation_tab.py @@ -26,8 +26,10 @@ import shutil import subprocess # nosec import sys +from typing import List import xml.etree.ElementTree as ET # nosec +from defusedxml.ElementTree import parse as defused_parse import defusedxml.minidom defusedxml.defuse_stdlib() @@ -89,7 +91,7 @@ def add_automation_tab( root = ET.Element("TabConfig") else: try: - tree = ET.parse(tab_config_file_path) # nosec + tree = defused_parse(tab_config_file_path) except ParseError as e: # pragma: no cover warnings.warn("Unable to parse %s\nError received = %s" % (tab_config_file_path, str(e))) return @@ -168,7 +170,7 @@ def remove_xml_tab(toolkit_dir, name, panel="Panel_PyAEDT_Extensions"): if not os.path.isfile(tab_config_file_path): # pragma: no cover return True try: - tree = ET.parse(tab_config_file_path) # nosec + tree = defused_parse(tab_config_file_path) except ParseError as e: # pragma: no cover warnings.warn("Unable to parse %s\nError received = %s" % (tab_config_file_path, str(e))) return @@ -278,6 +280,7 @@ def add_script_to_menu( d = list(_desktop_sessions.values())[0] personal_lib = d.personallib aedt_version = d.aedt_version_id + d.logger.error("WE ARE HERE 2.") if script_file and not os.path.exists(script_file): # pragma: no cover logger.error("Script does not exists.") return False @@ -352,6 +355,17 @@ def __tab_map(product): # pragma: no cover return product +def run_command(command: List[str], desktop_object): # pragma: no cover + """Run a command through subprocess.""" + try: + subprocess.run(command, check=True, capture_output=True, text=True) # nosec + except subprocess.CalledProcessError as e: + desktop_object.logger.error("Error occurred:", e.stderr) + return e.returncode + + return 0 + + def add_custom_toolkit(desktop_object, toolkit_name, wheel_toolkit=None, install=True): # pragma: no cover """Add toolkit to AEDT Automation Tab. @@ -370,6 +384,8 @@ def add_custom_toolkit(desktop_object, toolkit_name, wheel_toolkit=None, install ------- bool """ + print("WE ARE HERE.") + desktop_object.logger.error("WE ARE HERE.") toolkits = available_toolkits() toolkit_info = None product_name = None @@ -412,21 +428,6 @@ def add_custom_toolkit(desktop_object, toolkit_name, wheel_toolkit=None, install ) ) - def run_command(command): - try: - if is_linux: # pragma: no cover - process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec - else: - process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec - _, stderr = process.communicate() - ret_code = process.returncode - if ret_code != 0: - print("Error occurred:", stderr.decode("utf-8")) - return ret_code - except Exception as e: - print("Exception occurred:", str(e)) - return 1 # Return non-zero exit code for indicating an error - version = desktop_object.odesktop.GetVersion()[2:6].replace(".", "") if not is_linux: @@ -454,7 +455,8 @@ def run_command(command): if not os.path.exists(venv_dir): desktop_object.logger.info("Creating virtual environment") - run_command(f'"{base_venv}" -m venv "{venv_dir}" --system-site-packages') + command = [base_venv, "-m", "venv", venv_dir, "--system-site-packages"] + run_command(command, desktop_object) desktop_object.logger.info("Virtual environment created.") is_installed = False @@ -470,11 +472,13 @@ def run_command(command): is_installed = True if wheel_toolkit: wheel_toolkit = os.path.normpath(wheel_toolkit) - desktop_object.logger.info("Installing dependencies") + + desktop_object.logger.info(f"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(f'"{pip_exe}" uninstall --yes {toolkit_info["pip"]}') + command = [pip_exe, "uninstall", "--yes", toolkit_info["pip"]] + run_command(command, desktop_object) import zipfile unzipped_path = os.path.join( @@ -486,16 +490,20 @@ def run_command(command): zip_ref.extractall(unzipped_path) package_name = toolkit_info["package"] - run_command(f'"{pip_exe}" install --no-cache-dir --no-index --find-links={unzipped_path} {package_name}') + command = [pip_exe, "install", "--no-cache-dir", "--no-index", "--find-links={unzipped_path}", package_name] + run_command(command, desktop_object) elif install and not is_installed: # Install the specified package - run_command(f'"{pip_exe}" --default-timeout=1000 install {toolkit_info["pip"]}') + command = [pip_exe, "--default-timeout=1000", "install", toolkit_info["pip"]] + run_command(command, desktop_object) elif not install and is_installed: # Uninstall toolkit - run_command(f'"{pip_exe}" --default-timeout=1000 uninstall -y {toolkit_info["package"]}') + command = [pip_exe, "--default-timeout=1000", "uninstall", "-y", toolkit_info["package"]] + run_command(command, desktop_object) elif install and is_installed: # Update toolkit - run_command(f'"{pip_exe}" --default-timeout=1000 install {toolkit_info["pip"]} -U') + command = [pip_exe, "--default-timeout=1000", "install", toolkit_info["pip"], "-U"] + run_command(command, desktop_object) else: desktop_object.logger.info("Incorrect input") return @@ -531,7 +539,6 @@ def run_command(command): name=toolkit_info["name"], product=product_name, ) - desktop_object.logger.info("Installing dependencies") desktop_object.logger.info(f'{toolkit_info["name"]} uninstalled') diff --git a/src/ansys/aedt/core/workflows/project/create_report.py b/src/ansys/aedt/core/workflows/project/create_report.py index f7395deea38..db376b51bde 100644 --- a/src/ansys/aedt/core/workflows/project/create_report.py +++ b/src/ansys/aedt/core/workflows/project/create_report.py @@ -76,13 +76,14 @@ def main(extension_args): report.add_image(os.path.join(aedtapp.working_directory, plot.plot_name + ".jpg"), plot.plot_name) report.add_page_break() report.add_toc() - out = report.save_pdf(aedtapp.working_directory, "AEDT_Results.pdf") - aedtapp.logger.info(f"Report Generated. {out}") + pdf_path = report.save_pdf(aedtapp.working_directory, "AEDT_Results.pdf") + aedtapp.logger.info(f"Report Generated. {pdf_path}") if is_windows and not extension_args["is_test"]: # pragma: no cover try: - os.startfile(out) # nosec - except Exception: # pragma: no cover - aedtapp.logger.warning(f"Failed to open {out}") + if os.path.isfile(pdf_path) and pdf_path.endswith(".pdf"): + os.startfile(pdf_path) # nosec + except Exception: + aedtapp.logger.warning(f"Failed to open {pdf_path}") if not extension_args["is_test"]: # pragma: no cover app.release_desktop(False, False) From 41c9e2d5054037452be8231d05d94a92d927bde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Morais?= <146729917+SMoraisAnsys@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:02:24 +0100 Subject: [PATCH 2/6] FIX: Pyaedt installer in linux (#5392) Co-authored-by: Samuel Lopez <85613111+Samuelopez-ansys@users.noreply.github.com> --- .../Resources/pyaedt_installer_from_aedt.py | 176 ++++++++++-------- .../workflows/installer/extension_manager.py | 33 ---- .../core/workflows/templates/pyaedt_utils.py | 16 ++ .../templates/run_extension_manager.py_build | 82 ++++---- 4 files changed, 156 insertions(+), 151 deletions(-) diff --git a/doc/source/Resources/pyaedt_installer_from_aedt.py b/doc/source/Resources/pyaedt_installer_from_aedt.py index 8836d21d080..f92cffe78a6 100644 --- a/doc/source/Resources/pyaedt_installer_from_aedt.py +++ b/doc/source/Resources/pyaedt_installer_from_aedt.py @@ -28,26 +28,25 @@ import shutil import sys +try: + import subprocess +except ImportError: + import subprocessdotnet as subprocess + is_iron_python = platform.python_implementation().lower() == "ironpython" is_linux = os.name == "posix" is_windows = not is_linux -VENV_DIR_PREFIX = ".pyaedt_env" - - -def disclaimer(): - """Notify users about extra packages.""" - DISCLAIMER = ( - "This script will download and install certain third-party software and/or " - "open-source software (collectively, 'Third-Party Software'). Such Third-Party " - "Software is subject to separate terms and conditions and not the terms of your " - "Ansys software license agreement. Ansys does not warrant or support such " - "Third-Party Software." - ) - print(DISCLAIMER) - response = input("Do you want to proceed ? (y/n)").strip().lower() - return response == "y" +VENV_DIR_PREFIX = ".pyaedt_env" +DISCLAIMER = ( + "This script will download and install certain third-party software and/or " + "open-source software (collectively, 'Third-Party Software'). Such Third-Party " + "Software is subject to separate terms and conditions and not the terms of your " + "Ansys software license agreement. Ansys does not warrant or support such " + "Third-Party Software.\n" + "Do you want to proceed ?" +) def run_pyinstaller_from_c_python(oDesktop): @@ -75,7 +74,7 @@ def run_pyinstaller_from_c_python(oDesktop): # Launch this script again from the CPython interpreter. This calls the ``install_pyaedt()`` method, # which creates a virtual environment and installs PyAEDT and its dependencies - command = ['"{}"'.format(python_exe), '"{}"'.format(os.path.normpath(__file__)), "--version=" + version] + command = [python_exe, os.path.normpath(__file__), "--version=" + version] if is_student_version(oDesktop): command.append("--student") @@ -86,19 +85,11 @@ def run_pyinstaller_from_c_python(oDesktop): command.extend(['--wheel="{}"'.format(wheelpyaedt)]) oDesktop.AddMessage("", "", 0, "Installing PyAEDT.") - if is_windows: - import subprocess - - process = subprocess.Popen(" ".join(command)) - process.wait() - return_code = process.returncode - err_msg = "There was an error while installing PyAEDT." - else: - return_code = run_command(" ".join(command)) - err_msg = ( - "There was an error while installing PyAEDT. Refer to the Terminal window where AEDT was launched " "from." - ) + return_code = subprocess.call(command) + err_msg = "There was an error while installing PyAEDT." + if is_linux: + err_msg += " Refer to the Terminal window where AEDT was launched from." if str(return_code) != "0": oDesktop.AddMessage("", "", 2, err_msg) return @@ -148,7 +139,7 @@ def run_pyinstaller_from_c_python(oDesktop): command = r'"{}" "{}"'.format(python_exe, python_script) oDesktop.AddMessage("", "", 0, "Configuring PyAEDT panels in automation tab.") - ret_code = os.system(command) + ret_code = subprocess.call([python_exe, python_script]) if ret_code != 0: oDesktop.AddMessage("", "", 2, "Error occurred configuring the PyAEDT panels.") return @@ -223,16 +214,12 @@ def install_pyaedt(): args.edt_root, args.python_version.replace(".", "_") ) - response = disclaimer() - if not response: - exit(1) - if not os.path.exists(venv_dir): if args.version == "231": - run_command('"{}" -m venv "{}" --system-site-packages'.format(sys.executable, venv_dir)) + subprocess.call([sys.executable, "-m", "venv", venv_dir, "--system-site-packages"]) else: - run_command('"{}" -m venv "{}"'.format(sys.executable, venv_dir)) + subprocess.call([sys.executable, "-m", "venv", venv_dir]) if args.wheel and os.path.exists(args.wheel): wheel_pyaedt = args.wheel @@ -251,33 +238,44 @@ def install_pyaedt(): # Extracted folder. unzipped_path = wheel_pyaedt if args.version <= "231": - run_command( - '"{}" install --no-cache-dir --no-index --find-links={} pyaedt[all,dotnet]'.format( - pip_exe, unzipped_path - ) ) + subprocess.call( + [ + pip_exe, + "install", + "--no-cache-dir", + "--no-index", + "--find-links={}".format(unzipped_path), + "pyaedt[all,dotnet]", + ] + ) else: - run_command( - '"{}" install --no-cache-dir --no-index --find-links={} pyaedt[installer]'.format( - pip_exe, unzipped_path - ) + subprocess.call( + [ + pip_exe, + "install", + "--no-cache-dir", + "--no-index", + "--find-links={}".format(unzipped_path), + "pyaedt[installer]", + ] ) else: - run_command('"{}" -m pip install --upgrade pip'.format(python_exe)) - run_command('"{}" --default-timeout=1000 install wheel'.format(pip_exe)) + subprocess.call([python_exe, "-m", "pip", "install", "--upgrade", "pip"]) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "wheel"]) if args.version <= "231": - run_command('"{}" --default-timeout=1000 install pyaedt[all]=="0.9.0"'.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)) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "pyaedt[all]=='0.9.0'"]) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "jupyterlab"]) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "ipython", "-U"]) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "ipyvtklink"]) else: - run_command('"{}" --default-timeout=1000 install pyaedt[installer]'.format(pip_exe)) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "pyaedt[installer]"]) if args.version == "231": - run_command('"{}" uninstall -y pywin32'.format(pip_exe)) + subprocess.call([pip_exe, "uninstall", "-y", "pywin32"]) else: - run_command('"{}" uninstall --yes pyaedt'.format(pip_exe)) + subprocess.call([pip_exe, "uninstall", "-y", "pyaedt"]) if args.wheel and os.path.exists(args.wheel): wheel_pyaedt = args.wheel @@ -292,25 +290,35 @@ def install_pyaedt(): # Extract all contents to a directory. (You can specify a different extraction path if needed.) zip_ref.extractall(unzipped_path) if args.version <= "231": - run_command( - '"{}" install --no-cache-dir --no-index --find-links={} pyaedt[all]=="0.9.0"'.format( - pip_exe, unzipped_path - ) + subprocess.call( + [ + pip_exe, + "install", + "--no-cache-dir", + "--no-index", + "--find-links={}".format(unzipped_path), + "pyaedt[all]=='0.9.0'", + ] ) else: - run_command( - '"{}" install --no-cache-dir --no-index --find-links={} pyaedt[installer]'.format( - pip_exe, unzipped_path - ) + subprocess.call( + [ + pip_exe, + "install", + "--no-cache-dir", + "--no-index", + "--find-links={}".format(unzipped_path), + "pyaedt[installer]", + ] ) else: if args.version <= "231": - run_command('"{}" --default-timeout=1000 install pyaedt[all]=="0.9.0"'.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)) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "pyaedt[all]=='0.9.0'"]) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "jupyterlab"]) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "ipython", "-U"]) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "ipyvtklink"]) else: - run_command('"{}" --default-timeout=1000 install pyaedt[installer]'.format(pip_exe)) + subprocess.call([pip_exe, "--default-timeout=1000", "install", "pyaedt[installer]"]) sys.exit(0) @@ -322,24 +330,34 @@ def is_student_version(oDesktop): return False -def run_command(command): - if is_windows: - command = '"{}"'.format(command) - ret_code = os.system(command) - return ret_code +def validate_disclaimer(): + """Display dialog box and evaluate the response to the disclaimer.""" + from System.Windows.Forms import DialogResult + from System.Windows.Forms import MessageBox + from System.Windows.Forms import MessageBoxButtons + + response = MessageBox.Show(DISCLAIMER, "Disclaimer", MessageBoxButtons.YesNo) + return response == DialogResult.Yes if __name__ == "__main__": if is_iron_python: - # Check if wheelhouse defined. Wheelhouse is created for Windows only. - wheelpyaedt = [] - # Retrieve the script arguments - script_args = ScriptArgument.split() - if len(script_args) == 1: - wheelpyaedt = script_args[0] - if not os.path.exists(wheelpyaedt): - wheelpyaedt = [] - run_pyinstaller_from_c_python(oDesktop) + if "GetIsNonGraphical" in oDesktop.__dir__() and oDesktop.GetIsNonGraphical(): + print("When using IronPython, this script is expected to be run in graphical mode.") + sys.exit(1) + if validate_disclaimer(): + oDesktop.AddMessage("", "", 0, "Disclaimer accepted.") + # Check if wheelhouse defined. Wheelhouse is created for Windows only. + wheelpyaedt = [] + # Retrieve the script arguments + script_args = ScriptArgument.split() + if len(script_args) == 1: + wheelpyaedt = script_args[0] + if not os.path.exists(wheelpyaedt): + wheelpyaedt = [] + run_pyinstaller_from_c_python(oDesktop) + else: + oDesktop.AddMessage("", "", 1, "Disclaimer refused, installation canceled.") else: install_pyaedt() diff --git a/src/ansys/aedt/core/workflows/installer/extension_manager.py b/src/ansys/aedt/core/workflows/installer/extension_manager.py index d5a52976290..7435a06562f 100644 --- a/src/ansys/aedt/core/workflows/installer/extension_manager.py +++ b/src/ansys/aedt/core/workflows/installer/extension_manager.py @@ -318,36 +318,6 @@ def button_is_clicked( desktop.release_desktop(False, False) -def close_widget(widget): - """Close specific widget.""" - widget.destroy() - - -def create_disclaimer_window(root: tk.Tk): - """Notify users about extra packages.""" - DISCLAIMER = ( - "The extension manager will download and install certain third-party software and/or " - "open-source software (collectively, 'Third-Party Software'). Such Third-Party " - "Software is subject to separate terms and conditions and not the terms of your " - "Ansys software license agreement. Ansys does not warrant or support such " - "Third-Party Software. Do you still wish to proceed?" - ) - - disclaimer_window = tk.Toplevel(root) - disclaimer_window.title("Disclaimer") - disclaimer_window.grab_set() - disclaimer_window.protocol("WM_DELETE_WINDOW", lambda: None) - disclaimer_window.transient(root) - label = tk.Label(disclaimer_window, text=DISCLAIMER, wraplength=275) - label.pack() - yes_button = tk.Button(disclaimer_window, text="Yes", command=lambda: close_widget(disclaimer_window)) - yes_button.pack(side=tk.LEFT, padx=50, pady=10) - no_button = tk.Button(disclaimer_window, text="No", command=lambda: close_widget(root)) - no_button.pack(side=tk.RIGHT, padx=50, pady=10) - - return disclaimer_window - - root = tk.Tk() root.title("Extension Manager") @@ -390,9 +360,6 @@ def create_disclaimer_window(root: tk.Tk): root.geometry(f"{window_width}x{window_height}+{x_position}+{y_position}") -disclaimer_window = create_disclaimer_window(root) -disclaimer_window.geometry("300x170+{}+{}".format(x_position + 110, y_position + 45)) - # Create buttons in a 4x4 grid, centered for i, level in enumerate(toolkit_levels): row_num = i // 4 diff --git a/src/ansys/aedt/core/workflows/templates/pyaedt_utils.py b/src/ansys/aedt/core/workflows/templates/pyaedt_utils.py index 8f65431a8c8..22d1f7b1d81 100644 --- a/src/ansys/aedt/core/workflows/templates/pyaedt_utils.py +++ b/src/ansys/aedt/core/workflows/templates/pyaedt_utils.py @@ -31,6 +31,7 @@ import string import sys +from System.Windows.Forms import DialogResult from System.Windows.Forms import MessageBox from System.Windows.Forms import MessageBoxButtons from System.Windows.Forms import MessageBoxIcon @@ -147,3 +148,18 @@ def generate_unique_name(root_name, suffix="", n=6): if suffix: unique_name += suffix return unique_name + + +def validate_disclaimer(): + """Display dialog box and evaluate the response to the disclaimer.""" + DISCLAIMER = ( + "This script will download and install certain third-party software and/or " + "open-source software (collectively, 'Third-Party Software'). Such Third-Party " + "Software is subject to separate terms and conditions and not the terms of your " + "Ansys software license agreement. Ansys does not warrant or support such " + "Third-Party Software.\n" + "Do you want to proceed ?" + ) + + response = MessageBox.Show(DISCLAIMER, "Disclaimer", MessageBoxButtons.YesNo) + return response == DialogResult.Yes diff --git a/src/ansys/aedt/core/workflows/templates/run_extension_manager.py_build b/src/ansys/aedt/core/workflows/templates/run_extension_manager.py_build index 38811b98fcd..8ec46c8c71f 100644 --- a/src/ansys/aedt/core/workflows/templates/run_extension_manager.py_build +++ b/src/ansys/aedt/core/workflows/templates/run_extension_manager.py_build @@ -44,45 +44,49 @@ import pyaedt_utils def main(): - try: - # Get AEDT version - version_short = oDesktop.GetVersion()[2:6].replace(".", "") - # Launch extension manager - python_exe = r"##PYTHON_EXE##" % version - # Extensions directory - current_dir = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) - pyaedt_extensions_dir = os.path.normpath(os.path.join(current_dir, r"##TOOLKIT_REL_LIB_DIR##")) - pyaedt_script = os.path.join(pyaedt_extensions_dir, "extension_manager.py") - # Check if CPython interpreter and AEDT release match - python_exe = pyaedt_utils.sanitize_interpreter_path(python_exe, version_short) - # Check python executable - python_exe_flag = pyaedt_utils.check_file(python_exe, oDesktop) - if not python_exe_flag: - return - # Check script file - pyaedt_script_flag = pyaedt_utils.check_file(pyaedt_script, oDesktop) - if not pyaedt_script_flag: - return - # Add environment variables - pyaedt_utils.environment_variables(oDesktop) - # Open extension manager - if is_linux: - pyaedt_utils.set_ansys_em_environment(oDesktop) - 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) - except Exception as e: - pyaedt_utils.show_error(str(e), oDesktop) + if pyaedt_utils.validate_disclaimer(): + oDesktop.AddMessage("", "", 0, "Disclaimer accepted.") + try: + # Get AEDT version + version_short = oDesktop.GetVersion()[2:6].replace(".", "") + # Launch extension manager + python_exe = r"C:\Users\smorais\AppData\Roaming\.pyaedt_env\3_10\Scripts\python.exe" + # Extensions directory + current_dir = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + pyaedt_extensions_dir = os.path.normpath(os.path.join(current_dir, r"Lib")) + pyaedt_script = os.path.join(pyaedt_extensions_dir, "extension_manager.py") + # Check if CPython interpreter and AEDT release match + python_exe = pyaedt_utils.sanitize_interpreter_path(python_exe, version_short) + # Check python executable + python_exe_flag = pyaedt_utils.check_file(python_exe, oDesktop) + if not python_exe_flag: + return + # Check script file + pyaedt_script_flag = pyaedt_utils.check_file(pyaedt_script, oDesktop) + if not pyaedt_script_flag: + return + # Add environment variables + pyaedt_utils.environment_variables(oDesktop) + # Open extension manager + if is_linux: + pyaedt_utils.set_ansys_em_environment(oDesktop) + 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) + except Exception as e: + pyaedt_utils.show_error(str(e), oDesktop) + else: + oDesktop.AddMessage("", "", 1, "Disclaimer refused.") if __name__ == "__main__": From b240d8b13c2f2284669dd53f63ea66b6e8a32e20 Mon Sep 17 00:00:00 2001 From: Samuel Lopez <85613111+Samuelopez-ansys@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:48:47 +0100 Subject: [PATCH 3/6] FEAT: Display units option in X and Y axis (#5393) --- .../aedt/core/visualization/report/common.py | 23 +++++++++++++++++-- .../general/test_12_1_PostProcessing.py | 8 +++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/ansys/aedt/core/visualization/report/common.py b/src/ansys/aedt/core/visualization/report/common.py index a9270c5951a..2d4aa353ed6 100644 --- a/src/ansys/aedt/core/visualization/report/common.py +++ b/src/ansys/aedt/core/visualization/report/common.py @@ -1970,7 +1970,9 @@ def edit_grid( return self._change_property("Grid", "Grid", props) @pyaedt_function_handler() - def edit_x_axis(self, font="Arial", font_size=12, italic=False, bold=False, color=(0, 0, 0), label=None): + def edit_x_axis( + self, font="Arial", font_size=12, italic=False, bold=False, color=(0, 0, 0), label=None, display_units=True + ): """Edit the X-axis settings. Parameters @@ -1988,6 +1990,8 @@ def edit_x_axis(self, font="Arial", font_size=12, italic=False, bold=False, colo must be an integer in a range from 0 to 255. label : str, optional Label for the Y axis. The default is ``None``. + display_units : bool, optional + Whether to display units. The default is ``True``. Returns ------- @@ -2037,6 +2041,8 @@ def edit_x_axis(self, font="Arial", font_size=12, italic=False, bold=False, colo if label: props.append(["NAME:Name", "Value:=", label]) props.append(["NAME:Axis Color", "R:=", color[0], "G:=", color[1], "B:=", color[2]]) + props.append(["NAME:Display Units", "Value:=", display_units]) + return self._change_property("Axis", "AxisX", props) @pyaedt_function_handler() @@ -2157,7 +2163,17 @@ def hide_legend(self, solution_name=True, trace_name=True, variation_key=True, f return False @pyaedt_function_handler(axis_name="name") - def edit_y_axis(self, name="Y1", font="Arial", font_size=12, italic=False, bold=False, color=(0, 0, 0), label=None): + def edit_y_axis( + self, + name="Y1", + font="Arial", + font_size=12, + italic=False, + bold=False, + color=(0, 0, 0), + label=None, + display_units=True, + ): """Edit the Y-axis settings. Parameters @@ -2177,6 +2193,8 @@ def edit_y_axis(self, name="Y1", font="Arial", font_size=12, italic=False, bold= must be an integer in a range from 0 to 255. label : str, optional Label for the Y axis. The default is ``None``. + display_units : bool, optional + Whether to display units. The default is ``True``. Returns ------- @@ -2226,6 +2244,7 @@ def edit_y_axis(self, name="Y1", font="Arial", font_size=12, italic=False, bold= if label: props.append(["NAME:Name", "Value:=", label]) props.append(["NAME:Axis Color", "R:=", color[0], "G:=", color[1], "B:=", color[2]]) + props.append(["NAME:Display Units", "Value:=", display_units]) return self._change_property("Axis", "Axis" + name, props) @pyaedt_function_handler(axis_name="name") diff --git a/tests/system/general/test_12_1_PostProcessing.py b/tests/system/general/test_12_1_PostProcessing.py index d3aca395b7f..59374d48539 100644 --- a/tests/system/general/test_12_1_PostProcessing.py +++ b/tests/system/general/test_12_1_PostProcessing.py @@ -475,8 +475,12 @@ def test_09m_edit_properties(self): assert report.edit_grid(major_color=(0, 0, 125)) assert report.edit_grid(major_color=(0, 255, 0)) assert report.edit_grid(style_major="Dot") - assert report.edit_x_axis(font="Courier", font_size=14, italic=True, bold=False, color=(0, 128, 0)) - assert report.edit_y_axis(font="Courier", font_size=14, italic=True, bold=False, color=(0, 128, 0)) + assert report.edit_x_axis( + font="Bangers", font_size=14, italic=True, bold=False, color=(0, 128, 0), display_units=False + ) + assert report.edit_y_axis( + font="Bangers", font_size=14, italic=True, bold=False, color=(0, 128, 0), display_units=False + ) assert report.edit_x_axis( font="Courier", font_size=14, italic=True, bold=False, color=(0, 128, 0), label="Freq" ) From a785b23a946655045e2dd313c5cef7accdd3d092 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 11:16:26 +0100 Subject: [PATCH 4/6] BUILD: update scikit-rf requirement from <1.4,>=0.30.0 to >=0.30.0,<1.5 (#5397) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 19ea72e9b71..a463daad2a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ tests = [ "pyvista[io]>=0.38.0,<0.45", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", - "scikit-rf>=0.30.0,<1.4", + "scikit-rf>=0.30.0,<1.5", "SRTM.py", "utm", ] @@ -100,7 +100,7 @@ all = [ "fast-simplification>=0.1.7", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", - "scikit-rf>=0.30.0,<1.4", + "scikit-rf>=0.30.0,<1.5", "SRTM.py", "utm", ] @@ -113,7 +113,7 @@ installer = [ "pyvista[io]>=0.38.0,<0.45", # Never directly imported but required when loading ML related file see #4713 "scikit-learn>=1.0.0,<1.6", - "scikit-rf>=0.30.0,<1.4", + "scikit-rf>=0.30.0,<1.5", "SRTM.py", "utm", "jupyterlab>=3.6.0,<4.4", From ea93f1b249a17d873853f8e1718e4cd6ffe5f719 Mon Sep 17 00:00:00 2001 From: Hui Zhou Date: Mon, 11 Nov 2024 08:16:51 +0100 Subject: [PATCH 5/6] FIX: close UI by click X should not trigger parametrization (#5367) Co-authored-by: ring630 <@gmail.com> --- .../workflows/hfss3dlayout/parametrize_edb.py | 93 +++++++++++-------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/src/ansys/aedt/core/workflows/hfss3dlayout/parametrize_edb.py b/src/ansys/aedt/core/workflows/hfss3dlayout/parametrize_edb.py index fea6ef42692..ab7cd9413fd 100644 --- a/src/ansys/aedt/core/workflows/hfss3dlayout/parametrize_edb.py +++ b/src/ansys/aedt/core/workflows/hfss3dlayout/parametrize_edb.py @@ -57,6 +57,14 @@ def frontend(): # pragma: no cover + default_values = { + "layer": 0, + "material": 0, + "padstacks": 0, + "nets": 0, + "relative": 0, + } + app = ansys.aedt.core.Desktop( new_desktop=False, version=version, @@ -65,9 +73,13 @@ def frontend(): # pragma: no cover student_version=is_student, ) active_project = app.active_project() - active_design = app.active_design() - aedb_path = os.path.join(active_project.GetPath(), active_project.GetName() + ".aedb") - edb = Edb(aedb_path, active_design.GetName().split(";")[1], edbversion=version) + active_project_path = active_project.GetPath() + active_project_name = active_project.GetName() + aedb_path = os.path.join(active_project_path, active_project_name + ".aedb") + active_design_name = app.active_design().GetName().split(";")[1] + + app.release_desktop(False, False) + edb = Edb(aedb_path, active_design_name, edbversion=version) import tkinter from tkinter import ttk @@ -98,7 +110,7 @@ def frontend(): # pragma: no cover var9.set("New project name: ") label9.grid(row=0, column=0, pady=10) project_name = tkinter.Entry(master, width=30) - project_name.insert(tkinter.END, generate_unique_name(active_project.GetName(), n=2)) + project_name.insert(tkinter.END, generate_unique_name(active_project_name, n=2)) project_name.grid(row=0, column=1, pady=10, padx=5) var10 = tkinter.StringVar() @@ -108,7 +120,7 @@ def frontend(): # pragma: no cover relative = tkinter.IntVar() check5 = tkinter.Checkbutton(master, width=30, variable=relative) check5.grid(row=0, column=3, pady=10, padx=5) - relative.set(1) + relative.set(default_values["relative"]) var1 = tkinter.StringVar() label1 = tkinter.Label(master, textvariable=var1) @@ -117,7 +129,7 @@ def frontend(): # pragma: no cover layers = tkinter.IntVar() check1 = tkinter.Checkbutton(master, width=30, variable=layers) check1.grid(row=1, column=1, pady=10, padx=5) - layers.set(1) + layers.set(default_values["layer"]) var2 = tkinter.StringVar() label2 = tkinter.Label(master, textvariable=var2) @@ -126,7 +138,7 @@ def frontend(): # pragma: no cover materials = tkinter.IntVar() check2 = tkinter.Checkbutton(master, width=30, variable=materials) check2.grid(row=1, column=3, pady=10, padx=5) - materials.set(1) + materials.set(default_values["material"]) var3 = tkinter.StringVar() label3 = tkinter.Label(master, textvariable=var3) @@ -135,7 +147,7 @@ def frontend(): # pragma: no cover padstacks = tkinter.IntVar() check3 = tkinter.Checkbutton(master, width=30, variable=padstacks) check3.grid(row=2, column=1, pady=10, padx=5) - padstacks.set(1) + padstacks.set(default_values["padstacks"]) var5 = tkinter.StringVar() label5 = tkinter.Label(master, textvariable=var5) @@ -160,7 +172,7 @@ def frontend(): # pragma: no cover nets = tkinter.IntVar() check4 = tkinter.Checkbutton(master, width=30, variable=nets) check4.grid(row=4, column=1, pady=10, padx=5) - nets.set(1) + nets.set(default_values["nets"]) var8 = tkinter.StringVar() label8 = tkinter.Label(master, textvariable=var8) @@ -174,7 +186,10 @@ def frontend(): # pragma: no cover net_list.insert(idx, net) idx += 1 + master.flag = False + def callback(): + master.flag = True master.layers_ui = layers.get() master.materials_ui = materials.get() master.padstacks_ui = padstacks.get() @@ -191,34 +206,37 @@ def callback(): b = tkinter.Button(master, text="Create Parametric Model", width=40, command=callback) b.grid(row=5, column=1, pady=10) + edb.close_edb() tkinter.mainloop() - layers_ui = getattr(master, "layers_ui", extension_arguments["parametrize_layers"]) - materials_ui = getattr(master, "materials_ui", extension_arguments["parametrize_materials"]) - padstacks_ui = getattr(master, "padstacks_ui", extension_arguments["parametrize_padstacks"]) - nets_ui = getattr(master, "nets_ui", extension_arguments["parametrize_traces"]) - nets_filter_ui = getattr(master, "nets_filter", extension_arguments["nets_filter"]) - poly_ui = getattr(master, "poly_ui", extension_arguments["expansion_polygon_mm"]) - voids_ui = getattr(master, "voids_ui", extension_arguments["expansion_void_mm"]) - project_name_ui = getattr(master, "project_name_ui", extension_arguments["project_name"]) - relative_ui = getattr(master, "relative_ui", extension_arguments["relative_parametric"]) - - output_dict = { - "aedb_path": os.path.join(active_project.GetPath(), active_project.GetName() + ".aedb"), - "design_name": active_design.GetName().split(";")[1], - "parametrize_layers": layers_ui, - "parametrize_materials": materials_ui, - "parametrize_padstacks": padstacks_ui, - "parametrize_traces": nets_ui, - "nets_filter": nets_filter_ui, - "expansion_polygon_mm": float(poly_ui), - "expansion_void_mm": float(voids_ui), - "relative_parametric": relative_ui, - "project_name": project_name_ui, - } - edb.close_edb() - app.release_desktop(False, False) - return output_dict + if master.flag: + layers_ui = getattr(master, "layers_ui", extension_arguments["parametrize_layers"]) + materials_ui = getattr(master, "materials_ui", extension_arguments["parametrize_materials"]) + padstacks_ui = getattr(master, "padstacks_ui", extension_arguments["parametrize_padstacks"]) + nets_ui = getattr(master, "nets_ui", extension_arguments["parametrize_traces"]) + nets_filter_ui = getattr(master, "net_list_ui", extension_arguments["nets_filter"]) + poly_ui = getattr(master, "poly_ui", extension_arguments["expansion_polygon_mm"]) + voids_ui = getattr(master, "voids_ui", extension_arguments["expansion_void_mm"]) + project_name_ui = getattr(master, "project_name_ui", extension_arguments["project_name"]) + relative_ui = getattr(master, "relative_ui", extension_arguments["relative_parametric"]) + + output_dict = { + "aedb_path": os.path.join(active_project_path, active_project_name + ".aedb"), + "design_name": active_design_name, + "parametrize_layers": layers_ui, + "parametrize_materials": materials_ui, + "parametrize_padstacks": padstacks_ui, + "parametrize_traces": nets_ui, + "nets_filter": nets_filter_ui, + "expansion_polygon_mm": float(poly_ui), + "expansion_void_mm": float(voids_ui), + "relative_parametric": relative_ui, + "project_name": project_name_ui, + } + + return output_dict + else: + return False def main(extension_arguments): @@ -295,5 +313,6 @@ def main(extension_arguments): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) From c4a1475c435652748f099d4304421dc4324736ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Morais?= <146729917+SMoraisAnsys@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:52:49 +0100 Subject: [PATCH 6/6] CI: Change flags in codecov (#5395) --- .github/workflows/ci_cd.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index db85ca4f814..52d48431221 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -99,7 +99,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} name: codecov-unit-tests file: ./coverage.xml - flags: unit + flags: linux_unit - name: Upload pytest test results uses: actions/upload-artifact@v4 @@ -158,7 +158,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-solvers-tests-windows file: ./coverage.xml - flags: system,solvers,windows + flags: windows_system_solvers - name: Upload pytest test results uses: actions/upload-artifact@v4 @@ -214,7 +214,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-solvers-tests-linux file: ./coverage.xml - flags: system,solvers,linux + flags: linux_system_solvers - name: Upload pytest test results uses: actions/upload-artifact@v4 @@ -278,7 +278,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-general-tests-windows file: ./coverage.xml - flags: system,general,windows + flags: windows_system_general - name: Upload pytest test results uses: actions/upload-artifact@v4 @@ -346,7 +346,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} name: codecov-system-general-tests file: ./coverage.xml - flags: system,general,linux + flags: linux_system_general - name: Upload pytest test results uses: actions/upload-artifact@v4