From 3b7382fbce0a425d64b8c44e85cf5fd5b3a86370 Mon Sep 17 00:00:00 2001 From: kalisp Date: Thu, 21 Dec 2023 17:58:10 +0100 Subject: [PATCH 01/15] OP-7450 - WIP of new hook to install PySide2 Currently not working yet as subprocess is invoking wrong `pip` which causes issue about missing `dataclasses`. --- .../hosts/fusion/hooks/pre_fusion_setup.py | 3 + .../hosts/fusion/hooks/pre_pyside_install.py | 167 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 openpype/hosts/fusion/hooks/pre_pyside_install.py diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index 576628e8765..2afb2845608 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -64,5 +64,8 @@ def execute(self): self.launch_context.env[py3_var] = py3_dir + # for hook installing PySite2 + self.data["fusion_python3_home"] = py3_dir + self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py new file mode 100644 index 00000000000..791df335c7e --- /dev/null +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -0,0 +1,167 @@ +import os +import sys +import re +import subprocess +from platform import system +from openpype.lib.applications import PreLaunchHook, LaunchTypes + + +class InstallPySideToFusion(PreLaunchHook): + """Install Qt binding to fusion's python packages. + + Prelaunch hook does 2 things: + 1.) Fusion's python packages are pushed to the beginning of PYTHONPATH. + 2.) Check if fusion has installed PySide2 and will try to install if not. + + For pipeline implementation is required to have Qt binding installed in + fusion's python packages. + """ + + app_groups = {"fusion"} + order = 2 + launch_types = {LaunchTypes.local} + + def execute(self): + # Prelaunch hook is not crucial + try: + self.inner_execute() + except Exception: + self.log.warning( + "Processing of {} crashed.".format(self.__class__.__name__), + exc_info=True + ) + + def inner_execute(self, fusion_python3_home): + self.log.debug("Check for PySide2 installation.") + + fusion_python3_home = self.data.get("fusion_python3_home") + if not fusion_python3_home: + self.log.warning("'fusion_python3_home' was not provided. " + "Installation of PySide2 not possible") + return + + exe = "python.exe" if os.name == 'nt' else "python" + python_executable = os.path.join(fusion_python3_home, exe) + + if not os.path.exists(python_executable): + self.log.warning( + "Couldn't find python executable for fusion. {}".format( + pip_executable + ) + ) + return + + # Check if PySide2 is installed and skip if yes + if self.is_pyside_installed(python_executable): + self.log.debug("Fusion has already installed PySide2.") + return + + # Install PySide2 in fusion's python + if platform == "windows": + result = self.install_pyside_windows(python_executable) + else: + result = self.install_pyside(python_executable) + + if result: + self.log.info("Successfully installed PySide2 module to fusion.") + else: + self.log.warning("Failed to install PySide2 module to fusion.") + + def install_pyside_windows(self, python_executable): + """Install PySide2 python module to fusion's python. + + Installation requires administration rights that's why it is required + to use "pywin32" module which can execute command's and ask for + administration rights. + """ + try: + import win32api + import win32con + import win32process + import win32event + import pywintypes + from win32comext.shell.shell import ShellExecuteEx + from win32comext.shell import shellcon + except Exception: + self.log.warning("Couldn't import \"pywin32\" modules") + return + + try: + # Parameters + # - use "-m pip" as module pip to install PySide2 and argument + # "--ignore-installed" is to force install module to fusion's + # site-packages and make sure it is binary compatible + parameters = "-m pip install --ignore-installed PySide2" + + # Execute command and ask for administrator's rights + process_info = ShellExecuteEx( + nShow=win32con.SW_SHOWNORMAL, + fMask=shellcon.SEE_MASK_NOCLOSEPROCESS, + lpVerb="runas", + lpFile=python_executable, + lpParameters=parameters, + lpDirectory=os.path.dirname(python_executable) + ) + process_handle = process_info["hProcess"] + win32event.WaitForSingleObject(process_handle, win32event.INFINITE) + returncode = win32process.GetExitCodeProcess(process_handle) + return returncode == 0 + except pywintypes.error: + pass + + def install_pyside(self, python_executable): + """Install PySide2 python module to fusion's python.""" + try: + # Parameters + # - use "-m pip" as module pip to install PySide2 and argument + # "--ignore-installed" is to force install module to fusion's + # site-packages and make sure it is binary compatible + args = [ + python_executable, + "-m", + "pip", + "install", + "--ignore-installed", + "PySide2", + ] + process = subprocess.Popen( + args, stdout=subprocess.PIPE, universal_newlines=True + ) + process.communicate() + return process.returncode == 0 + except PermissionError: + self.log.warning( + "Permission denied with command:" + "\"{}\".".format(" ".join(args)) + ) + except OSError as error: + self.log.warning(f"OS error has occurred: \"{error}\".") + except subprocess.SubprocessError: + pass + + def is_pyside_installed(self, python_executable): + """Check if PySide2 module is in fusion's pip list. + + Check that PySide2 is installed directly in fusion's site-packages. + It is possible that it is installed in user's site-packages but that + may be incompatible with fusion's python. + """ + # Get pip list from fusion's python executable + args = [python_executable, "-m", "pip", "list"] + process = subprocess.Popen(args, stdout=subprocess.PIPE) + stdout, _ = process.communicate() + lines = stdout.decode().split(os.linesep) + # Second line contain dashes that define maximum length of module name. + # Second column of dashes define maximum length of module version. + package_dashes, *_ = lines[1].split(" ") + package_len = len(package_dashes) + + # Got through printed lines starting at line 3 + for idx in range(2, len(lines)): + line = lines[idx] + if not line: + continue + package_name = line[0:package_len].strip() + if package_name.lower() == "pyside2": + return True + return False From 61d48410f1f91d8d0c2f0de3ce42fd147e227c96 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 8 Jan 2024 14:11:27 +0100 Subject: [PATCH 02/15] OP-7450 - updates querying of PySide2 presence Cannot use pip list as wrong pip from .venv is used and it was causing issue about missing dataclass (not in Python3.6). This implementation is simpler and just tries to import PySide2. --- .../hosts/fusion/hooks/pre_pyside_install.py | 55 +++++++------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index 791df335c7e..8b09834992f 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -1,17 +1,13 @@ import os -import sys -import re import subprocess -from platform import system +import platform from openpype.lib.applications import PreLaunchHook, LaunchTypes class InstallPySideToFusion(PreLaunchHook): - """Install Qt binding to fusion's python packages. + """Automatically installs Qt binding to fusion's python packages. - Prelaunch hook does 2 things: - 1.) Fusion's python packages are pushed to the beginning of PYTHONPATH. - 2.) Check if fusion has installed PySide2 and will try to install if not. + Check if fusion has installed PySide2 and will try to install if not. For pipeline implementation is required to have Qt binding installed in fusion's python packages. @@ -30,8 +26,9 @@ def execute(self): "Processing of {} crashed.".format(self.__class__.__name__), exc_info=True ) + raise - def inner_execute(self, fusion_python3_home): + def inner_execute(self): self.log.debug("Check for PySide2 installation.") fusion_python3_home = self.data.get("fusion_python3_home") @@ -46,7 +43,7 @@ def inner_execute(self, fusion_python3_home): if not os.path.exists(python_executable): self.log.warning( "Couldn't find python executable for fusion. {}".format( - pip_executable + python_executable ) ) return @@ -56,8 +53,9 @@ def inner_execute(self, fusion_python3_home): self.log.debug("Fusion has already installed PySide2.") return + self.log.debug("Installing PySide2.") # Install PySide2 in fusion's python - if platform == "windows": + if platform.system().lower() == "windows": result = self.install_pyside_windows(python_executable) else: result = self.install_pyside(python_executable) @@ -103,7 +101,8 @@ def install_pyside_windows(self, python_executable): lpDirectory=os.path.dirname(python_executable) ) process_handle = process_info["hProcess"] - win32event.WaitForSingleObject(process_handle, win32event.INFINITE) + win32event.WaitForSingleObject(process_handle, + win32event.INFINITE) returncode = win32process.GetExitCodeProcess(process_handle) return returncode == 0 except pywintypes.error: @@ -140,28 +139,14 @@ def install_pyside(self, python_executable): pass def is_pyside_installed(self, python_executable): - """Check if PySide2 module is in fusion's pip list. + """Check if PySide2 module is in fusion's pip list.""" + args = [python_executable, "-c", "import PySide2"] + process = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + _, stderr = process.communicate() + stderr = stderr.decode() + if "ModuleNotFound" in stderr: + return False + return True - Check that PySide2 is installed directly in fusion's site-packages. - It is possible that it is installed in user's site-packages but that - may be incompatible with fusion's python. - """ - # Get pip list from fusion's python executable - args = [python_executable, "-m", "pip", "list"] - process = subprocess.Popen(args, stdout=subprocess.PIPE) - stdout, _ = process.communicate() - lines = stdout.decode().split(os.linesep) - # Second line contain dashes that define maximum length of module name. - # Second column of dashes define maximum length of module version. - package_dashes, *_ = lines[1].split(" ") - package_len = len(package_dashes) - - # Got through printed lines starting at line 3 - for idx in range(2, len(lines)): - line = lines[idx] - if not line: - continue - package_name = line[0:package_len].strip() - if package_name.lower() == "pyside2": - return True - return False From a2e63bc3d3e381e3299bc3e51f85420e5b856ce0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 8 Jan 2024 14:20:23 +0100 Subject: [PATCH 03/15] OP-7450 - typo --- openpype/hosts/fusion/hooks/pre_fusion_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index 2afb2845608..3da89687276 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -64,7 +64,7 @@ def execute(self): self.launch_context.env[py3_var] = py3_dir - # for hook installing PySite2 + # for hook installing PySide2 self.data["fusion_python3_home"] = py3_dir self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") From a242723930a9dc1f7ca16a4e6782f9b7e3458681 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 8 Jan 2024 15:12:17 +0100 Subject: [PATCH 04/15] OP-7450 - removed forgotten raise for debugging --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index 8b09834992f..fa6f2b31662 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -26,7 +26,6 @@ def execute(self): "Processing of {} crashed.".format(self.__class__.__name__), exc_info=True ) - raise def inner_execute(self): self.log.debug("Check for PySide2 installation.") @@ -149,4 +148,3 @@ def is_pyside_installed(self, python_executable): if "ModuleNotFound" in stderr: return False return True - From 65b7a6d613046ea70b06b3182278777144ceeb21 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 8 Jan 2024 15:13:14 +0100 Subject: [PATCH 05/15] OP-7450 - double quotes Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index fa6f2b31662..ae7c3357c55 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -36,7 +36,7 @@ def inner_execute(self): "Installation of PySide2 not possible") return - exe = "python.exe" if os.name == 'nt' else "python" + exe = "python.exe" if os.name == "nt" else "python" python_executable = os.path.join(fusion_python3_home, exe) if not os.path.exists(python_executable): From 05ff9274c0ed2ce40be16b748500728a36e80df7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 8 Jan 2024 15:14:12 +0100 Subject: [PATCH 06/15] OP-7450 - return if error Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index ae7c3357c55..c6b3e1c32af 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -105,7 +105,7 @@ def install_pyside_windows(self, python_executable): returncode = win32process.GetExitCodeProcess(process_handle) return returncode == 0 except pywintypes.error: - pass + return False def install_pyside(self, python_executable): """Install PySide2 python module to fusion's python.""" From 6c5ba33a84e4b412a5fd5d1336cd097c3c0d6c7f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 8 Jan 2024 15:14:29 +0100 Subject: [PATCH 07/15] OP-7450 - return False Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index c6b3e1c32af..7e1d2940f93 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -81,7 +81,7 @@ def install_pyside_windows(self, python_executable): from win32comext.shell import shellcon except Exception: self.log.warning("Couldn't import \"pywin32\" modules") - return + return False try: # Parameters From f546b6306c5bffca5a9f9104fc54e880277f052e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 9 Jan 2024 12:38:36 +0100 Subject: [PATCH 08/15] OP-7450 - added optionality for InstallPySideToFusion New hook is controllable by Settings. --- .../hosts/fusion/hooks/pre_pyside_install.py | 3 ++ server_addon/fusion/server/settings.py | 49 +++++++++++++------ server_addon/fusion/server/version.py | 2 +- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index fa6f2b31662..2d3e5a66d1c 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -20,6 +20,9 @@ class InstallPySideToFusion(PreLaunchHook): def execute(self): # Prelaunch hook is not crucial try: + settings = self.data["project_settings"][self.host_name] + if not settings["hooks"]["InstallPySideToFusion"]["enabled"]: + return self.inner_execute() except Exception: self.log.warning( diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index 1bc12773d20..21189b390eb 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -25,16 +25,6 @@ def _create_saver_instance_attributes_enum(): ] -def _image_format_enum(): - return [ - {"value": "exr", "label": "exr"}, - {"value": "tga", "label": "tga"}, - {"value": "png", "label": "png"}, - {"value": "tif", "label": "tif"}, - {"value": "jpg", "label": "jpg"}, - ] - - class CreateSaverPluginModel(BaseSettingsModel): _isGroup = True temp_rendering_path_template: str = Field( @@ -49,9 +39,23 @@ class CreateSaverPluginModel(BaseSettingsModel): enum_resolver=_create_saver_instance_attributes_enum, title="Instance attributes" ) - image_format: str = Field( - enum_resolver=_image_format_enum, - title="Output Image Format" + output_formats: list[str] = Field( + default_factory=list, + title="Output formats" + ) + + +class HookOptionalModel(BaseSettingsModel): + enabled: bool = Field( + True, + title="Enabled" + ) + + +class HooksModel(BaseSettingsModel): + InstallPySideToFusion: HookOptionalModel = Field( + default_factory=HookOptionalModel, + title="Install PySide2" ) @@ -71,6 +75,10 @@ class FusionSettings(BaseSettingsModel): default_factory=CopyFusionSettingsModel, title="Local Fusion profile settings" ) + hooks: HooksModel = Field( + default_factory=HooksModel, + title="Hooks" + ) create: CreatPluginsModel = Field( default_factory=CreatPluginsModel, title="Creator plugins" @@ -93,6 +101,11 @@ class FusionSettings(BaseSettingsModel): "copy_status": False, "force_sync": False }, + "hooks": { + "InstallPySideToFusion": { + "enabled": True + } + }, "create": { "CreateSaver": { "temp_rendering_path_template": "{workdir}/renders/fusion/{product[name]}/{product[name]}.{frame}.{ext}", @@ -104,7 +117,15 @@ class FusionSettings(BaseSettingsModel): "reviewable", "farm_rendering" ], - "image_format": "exr" + "output_formats": [ + "exr", + "jpg", + "jpeg", + "jpg", + "tiff", + "png", + "tga" + ] } } } diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py index 485f44ac21b..b3f4756216d 100644 --- a/server_addon/fusion/server/version.py +++ b/server_addon/fusion/server/version.py @@ -1 +1 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" From edef54f09269e6be6874697655a259afdc454e21 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 9 Jan 2024 14:01:14 +0100 Subject: [PATCH 09/15] OP-7450 - updated querying of Qt This approach should be more generic, not tied to specific version of PySide2 --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index d2a6dd0b7b4..b66d5c21752 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -4,7 +4,7 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes -class InstallPySideToFusion(PreLaunchHook): +class l(PreLaunchHook): """Automatically installs Qt binding to fusion's python packages. Check if fusion has installed PySide2 and will try to install if not. @@ -142,12 +142,12 @@ def install_pyside(self, python_executable): def is_pyside_installed(self, python_executable): """Check if PySide2 module is in fusion's pip list.""" - args = [python_executable, "-c", "import PySide2"] + args = [python_executable, "-c", "from qtpy import QtWidgets"] process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _, stderr = process.communicate() stderr = stderr.decode() - if "ModuleNotFound" in stderr: + if stderr: return False return True From ee66b0617670d2cbb9a30d17f5db4897f64d49fa Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 9 Jan 2024 14:08:18 +0100 Subject: [PATCH 10/15] OP-7450 - fix unwanted change --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index b66d5c21752..14991982e13 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -4,7 +4,7 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes -class l(PreLaunchHook): +class InstallPySideToFusion(PreLaunchHook): """Automatically installs Qt binding to fusion's python packages. Check if fusion has installed PySide2 and will try to install if not. From 77c2a583f3fef6add9becd1e7585090d9c57eb9a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 9 Jan 2024 16:01:45 +0100 Subject: [PATCH 11/15] OP-7450 - added settings for legacy OP --- .../defaults/project_settings/fusion.json | 5 ++++ .../schema_project_fusion.json | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index 0edcae060a1..85794426252 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -15,6 +15,11 @@ "copy_status": false, "force_sync": false }, + "hooks": { + "InstallPySideToFusion": { + "enabled": true + } + }, "create": { "CreateSaver": { "temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 5177d8bc7c7..fbd856b8952 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -41,6 +41,29 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "hooks", + "label": "Hooks", + "children": [ + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "InstallPySideToFusion", + "label": "Install PySide2", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + } + ] + }, { "type": "dict", "collapsible": true, From 28a412b29b4b752b2b03a4786360c633e065c7f3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 9 Jan 2024 17:17:53 +0100 Subject: [PATCH 12/15] OP-7450 - use correct python executable name in Linux Because it is not "expected" python in blender but installed python, I would expect the executable is python3 on linux/macos rather than python. Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index 14991982e13..8746a844178 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -39,8 +39,15 @@ def inner_execute(self): "Installation of PySide2 not possible") return - exe = "python.exe" if os.name == "nt" else "python" - python_executable = os.path.join(fusion_python3_home, exe) + if platform.system().lower() == "windows": + exe_filenames = ["python.exe"] + else: + exe_filenames = ["python3", "python"] + + for exe_filename in exe_filenames: + python_executable = os.path.join(fusion_python3_home, exe_filename) + if os.path.exists(python_executable): + break if not os.path.exists(python_executable): self.log.warning( From eac5e2938f482ae3bf7ad371ef0c508245667cd5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 10 Jan 2024 13:02:29 +0100 Subject: [PATCH 13/15] OP-7450 - headless installation in Windows It checks first that it would need admin privileges for installation, if not it installs headlessly. If yes, it will create separate dialog that will ask for admin privileges. --- .../hosts/fusion/hooks/pre_pyside_install.py | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index 8746a844178..2a252b8007c 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -1,6 +1,8 @@ import os import subprocess import platform +import uuid + from openpype.lib.applications import PreLaunchHook, LaunchTypes @@ -58,23 +60,24 @@ def inner_execute(self): return # Check if PySide2 is installed and skip if yes - if self.is_pyside_installed(python_executable): + if self._is_pyside_installed(python_executable): self.log.debug("Fusion has already installed PySide2.") return self.log.debug("Installing PySide2.") # Install PySide2 in fusion's python - if platform.system().lower() == "windows": - result = self.install_pyside_windows(python_executable) + if self._windows_require_permissions( + os.path.dirname(python_executable)): + result = self._install_pyside_windows(python_executable) else: - result = self.install_pyside(python_executable) + result = self._install_pyside(python_executable) if result: self.log.info("Successfully installed PySide2 module to fusion.") else: self.log.warning("Failed to install PySide2 module to fusion.") - def install_pyside_windows(self, python_executable): + def _install_pyside_windows(self, python_executable): """Install PySide2 python module to fusion's python. Installation requires administration rights that's why it is required @@ -117,13 +120,15 @@ def install_pyside_windows(self, python_executable): except pywintypes.error: return False - def install_pyside(self, python_executable): + def _install_pyside(self, python_executable): """Install PySide2 python module to fusion's python.""" try: # Parameters # - use "-m pip" as module pip to install PySide2 and argument # "--ignore-installed" is to force install module to fusion's # site-packages and make sure it is binary compatible + env = dict(os.environ) + del env['PYTHONPATH'] args = [ python_executable, "-m", @@ -133,7 +138,8 @@ def install_pyside(self, python_executable): "PySide2", ] process = subprocess.Popen( - args, stdout=subprocess.PIPE, universal_newlines=True + args, stdout=subprocess.PIPE, universal_newlines=True, + env=env ) process.communicate() return process.returncode == 0 @@ -147,7 +153,7 @@ def install_pyside(self, python_executable): except subprocess.SubprocessError: pass - def is_pyside_installed(self, python_executable): + def _is_pyside_installed(self, python_executable): """Check if PySide2 module is in fusion's pip list.""" args = [python_executable, "-c", "from qtpy import QtWidgets"] process = subprocess.Popen(args, @@ -158,3 +164,22 @@ def is_pyside_installed(self, python_executable): if stderr: return False return True + + def _windows_require_permissions(self, dirpath): + try: + # Attempt to create a temporary file in the folder + temp_file_path = os.path.join(dirpath, uuid.uuid4().hex) + with open(temp_file_path, "w"): + pass + os.remove(temp_file_path) # Clean up temporary file + return False + + except PermissionError: + return True + + except BaseException as exc: + print(( + "Failed to determine if root requires permissions." + "Unexpected error: {}" + ).format(exc)) + return False From f85e484ed800ae3761e719821a95023646c7f48a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 10 Jan 2024 13:04:02 +0100 Subject: [PATCH 14/15] OP-7450 - Hound --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index 2a252b8007c..ae341c7590a 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -178,8 +178,6 @@ def _windows_require_permissions(self, dirpath): return True except BaseException as exc: - print(( - "Failed to determine if root requires permissions." - "Unexpected error: {}" - ).format(exc)) + print(("Failed to determine if root requires permissions." + "Unexpected error: {}").format(exc)) return False From 004da4adbdb8be8dd64d005bc1b8d49534c6f808 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 10 Jan 2024 17:06:40 +0100 Subject: [PATCH 15/15] Update openpype/hosts/fusion/hooks/pre_pyside_install.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/fusion/hooks/pre_pyside_install.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/fusion/hooks/pre_pyside_install.py b/openpype/hosts/fusion/hooks/pre_pyside_install.py index ae341c7590a..f98aeda2339 100644 --- a/openpype/hosts/fusion/hooks/pre_pyside_install.py +++ b/openpype/hosts/fusion/hooks/pre_pyside_install.py @@ -166,6 +166,9 @@ def _is_pyside_installed(self, python_executable): return True def _windows_require_permissions(self, dirpath): + if platform.system().lower() != "windows": + return False + try: # Attempt to create a temporary file in the folder temp_file_path = os.path.join(dirpath, uuid.uuid4().hex)