From 8b0036cf53426068ac855f411e18b3e6ab418c30 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 9 Jun 2021 18:19:40 +0200 Subject: [PATCH 001/110] fix unreal launch on linux --- openpype/hosts/unreal/api/lib.py | 212 +++++++++++------- .../unreal/hooks/pre_workfile_preparation.py | 10 +- 2 files changed, 139 insertions(+), 83 deletions(-) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 7e706a2789..3efc0c63be 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -1,38 +1,50 @@ +# -*- coding: utf-8 -*- +"""Unreal launching and project tools.""" import sys import os import platform import json from distutils import dir_util import subprocess +import re +from collections import OrderedDict from openpype.api import get_project_settings -def get_engine_versions(): - """ +def get_engine_versions(env=None): + """Detect Unreal Engine versions. + This will try to detect location and versions of installed Unreal Engine. Location can be overridden by `UNREAL_ENGINE_LOCATION` environment variable. - Returns: + Args: + env (dict, optional): Environment to use. - dict: dictionary with version as a key and dir as value. + Returns: + OrderedDict: dictionary with version as a key and dir as value. + so the highest version is first. Example: - - >>> get_engine_version() + >>> get_engine_versions() { "4.23": "C:/Epic Games/UE_4.23", "4.24": "C:/Epic Games/UE_4.24" } - """ - try: - engine_locations = {} - root, dirs, files = next(os.walk(os.environ["UNREAL_ENGINE_LOCATION"])) - for dir in dirs: - if dir.startswith("UE_"): - ver = dir.split("_")[1] - engine_locations[ver] = os.path.join(root, dir) + """ + env = env or os.environ + engine_locations = {} + try: + root, dirs, _ = next(os.walk(env["UNREAL_ENGINE_LOCATION"])) + + for directory in dirs: + if directory.startswith("UE"): + try: + ver = re.split(r"[-_]", directory)[1] + except IndexError: + continue + engine_locations[ver] = os.path.join(root, directory) except KeyError: # environment variable not set pass @@ -40,32 +52,37 @@ def get_engine_versions(): # specified directory doesn't exists pass - # if we've got something, terminate autodetection process + # if we've got something, terminate auto-detection process if engine_locations: - return engine_locations + return OrderedDict(sorted(engine_locations.items())) # else kick in platform specific detection if platform.system().lower() == "windows": - return _win_get_engine_versions() + return OrderedDict(sorted(_win_get_engine_versions().items())) elif platform.system().lower() == "linux": # on linux, there is no installation and getting Unreal Engine involves # git clone. So we'll probably depend on `UNREAL_ENGINE_LOCATION`. pass elif platform.system().lower() == "darwin": - return _darwin_get_engine_version() + return OrderedDict(sorted(_darwin_get_engine_version(env).items())) - return {} + return OrderedDict() def _win_get_engine_versions(): - """ + """Get Unreal Engine versions on Windows. + If engines are installed via Epic Games Launcher then there is: `%PROGRAMDATA%/Epic/UnrealEngineLauncher/LauncherInstalled.dat` This file is JSON file listing installed stuff, Unreal engines are marked with `"AppName" = "UE_X.XX"`` like `UE_4.24` + + Returns: + dict: version as a key and path as a value. + """ install_json_path = os.path.join( - os.environ.get("PROGRAMDATA"), + os.getenv("PROGRAMDATA"), "Epic", "UnrealEngineLauncher", "LauncherInstalled.dat", @@ -75,11 +92,19 @@ def _win_get_engine_versions(): def _darwin_get_engine_version() -> dict: - """ + """Get Unreal Engine versions on MacOS. + It works the same as on Windows, just JSON file location is different. + + Returns: + dict: version as a key and path as a value. + + See Aslo: + :func:`_win_get_engine_versions`. + """ install_json_path = os.path.join( - os.environ.get("HOME"), + os.getenv("HOME"), "Library", "Application Support", "Epic", @@ -91,25 +116,26 @@ def _darwin_get_engine_version() -> dict: def _parse_launcher_locations(install_json_path: str) -> dict: - """ - This will parse locations from json file. + """This will parse locations from json file. + + Args: + install_json_path (str): Path to `LauncherInstalled.dat`. + + Returns: + dict: with unreal engine versions as keys and + paths to those engine installations as value. - :param install_json_path: path to `LauncherInstalled.dat` - :type install_json_path: str - :returns: returns dict with unreal engine versions as keys and - paths to those engine installations as value. - :rtype: dict """ engine_locations = {} if os.path.isfile(install_json_path): with open(install_json_path, "r") as ilf: try: install_data = json.load(ilf) - except json.JSONDecodeError: + except json.JSONDecodeError as e: raise Exception( "Invalid `LauncherInstalled.dat file. `" "Cannot determine Unreal Engine location." - ) + ) from e for installation in install_data.get("InstallationList", []): if installation.get("AppName").startswith("UE_"): @@ -123,36 +149,43 @@ def create_unreal_project(project_name: str, ue_version: str, pr_dir: str, engine_path: str, - dev_mode: bool = False) -> None: - """ - This will create `.uproject` file at specified location. As there is no - way I know to create project via command line, this is easiest option. - Unreal project file is basically JSON file. If we find + dev_mode: bool = False, + env: dict = None) -> None: + """This will create `.uproject` file at specified location. + + As there is no way I know to create project via command line, this is + easiest option. Unreal project file is basically JSON file. If we find `AVALON_UNREAL_PLUGIN` environment variable we assume this is location of Avalon Integration Plugin and we copy its content to project folder and enable this plugin. - :param project_name: project name - :type project_name: str - :param ue_version: unreal engine version (like 4.23) - :type ue_version: str - :param pr_dir: path to directory where project will be created - :type pr_dir: str - :param engine_path: Path to Unreal Engine installation - :type engine_path: str - :param dev_mode: Flag to trigger C++ style Unreal project needing - Visual Studio and other tools to compile plugins from - sources. This will trigger automatically if `Binaries` - directory is not found in plugin folders as this indicates - this is only source distribution of the plugin. Dev mode - is also set by preset file `unreal/project_setup.json` in - **OPENPYPE_CONFIG**. - :type dev_mode: bool - :returns: None - """ - preset = get_project_settings(project_name)["unreal"]["project_setup"] + Args: + project_name (str): Name of the project. + ue_version (str): Unreal engine version (like 4.23). + pr_dir (str): Path to directory where project will be created. + engine_path (str): Path to Unreal Engine installation. + dev_mode (bool, optional): Flag to trigger C++ style Unreal project + needing Visual Studio and other tools to compile plugins from + sources. This will trigger automatically if `Binaries` + directory is not found in plugin folders as this indicates + this is only source distribution of the plugin. Dev mode + is also set by preset file `unreal/project_setup.json` in + **OPENPYPE_CONFIG**. + env (dict, optional): Environment to use. If not set, `os.environ`. - if os.path.isdir(os.environ.get("AVALON_UNREAL_PLUGIN", "")): + Throws: + NotImplemented: For unsupported platforms. + + Returns: + None + + """ + env = env or os.environ + preset = get_project_settings(project_name)["unreal"]["project_setup"] + plugins_path = None + uep_path = None + + if os.path.isdir(env.get("AVALON_UNREAL_PLUGIN", "")): # copy plugin to correct path under project plugins_path = os.path.join(pr_dir, "Plugins") avalon_plugin_path = os.path.join(plugins_path, "Avalon") @@ -180,17 +213,17 @@ def create_unreal_project(project_name: str, } if preset["install_unreal_python_engine"]: - # If `OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from there - # to support offline installation. + # If `PYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from there to + # support offline installation. # Otherwise clone UnrealEnginePython to Plugins directory # https://github.com/20tab/UnrealEnginePython.git uep_path = os.path.join(plugins_path, "UnrealEnginePython") - if os.environ.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"): + if env.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"): os.makedirs(uep_path, exist_ok=True) dir_util._path_created = {} dir_util.copy_tree( - os.environ.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"), + env.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"), uep_path) else: # WARNING: this will trigger dev_mode, because we need to compile @@ -246,25 +279,40 @@ def create_unreal_project(project_name: str, with open(project_file, mode="w") as pf: json.dump(data, pf, indent=4) - # UE < 4.26 have Python2 by default, so we need PySide - # but we will not need it in 4.26 and up - if int(ue_version.split(".")[1]) < 26: - # ensure we have PySide installed in engine - # TODO: make it work for other platforms 🍎 🐧 + # ensure we have PySide installed in engine + # this won't work probably as pyside is no longer on pypi + # DEPRECATED: support for python 2 in UE4 is dropped. + python_path = None + if int(ue_version.split(".")[0]) == 4 and \ + int(ue_version.split(".")[1]) < 25: if platform.system().lower() == "windows": python_path = os.path.join(engine_path, "Engine", "Binaries", "ThirdParty", "Python", "Win64", "python.exe") + if platform.system().lower() == "linux": + python_path = os.path.join(engine_path, "Engine", "Binaries", + "ThirdParty", "Python", "Linux", + "bin", "python") + + if platform.system().lower() == "darwin": + python_path = os.path.join(engine_path, "Engine", "Binaries", + "ThirdParty", "Python", "Mac", + "bin", "python") + + if python_path: subprocess.run([python_path, "-m", "pip", "install", "pyside"]) + else: + raise NotImplemented("Unsupported platform") if dev_mode or preset["dev_mode"]: _prepare_cpp_project(project_file, engine_path) def _prepare_cpp_project(project_file: str, engine_path: str) -> None: - """ + """Prepare CPP Unreal Project. + This function will add source files needed for project to be rebuild along with the avalon integration plugin. @@ -273,12 +321,12 @@ def _prepare_cpp_project(project_file: str, engine_path: str) -> None: by some generator. This needs more research as manually writing those files is rather hackish. :skull_and_crossbones: - :param project_file: path to .uproject file - :type project_file: str - :param engine_path: path to unreal engine associated with project - :type engine_path: str - """ + Args: + project_file (str): Path to .uproject file. + engine_path (str): Path to unreal engine associated with project. + + """ project_name = os.path.splitext(os.path.basename(project_file))[0] project_dir = os.path.dirname(project_file) targets_dir = os.path.join(project_dir, "Source") @@ -388,19 +436,23 @@ class {1}_API A{0}GameModeBase : public AGameModeBase sources_dir, f"{project_name}GameModeBase.h"), mode="w") as f: f.write(game_mode_h) + u_build_tool = (f"{engine_path}/Engine/Binaries/DotNET/" + "UnrealBuildTool.exe") + u_header_tool = None + if platform.system().lower() == "windows": - u_build_tool = (f"{engine_path}/Engine/Binaries/DotNET/" - "UnrealBuildTool.exe") u_header_tool = (f"{engine_path}/Engine/Binaries/Win64/" f"UnrealHeaderTool.exe") elif platform.system().lower() == "linux": - # WARNING: there is no UnrealBuildTool on linux? - u_build_tool = "" - u_header_tool = "" + u_header_tool = (f"{engine_path}/Engine/Binaries/Linux/" + f"UnrealHeaderTool") elif platform.system().lower() == "darwin": - # WARNING: there is no UnrealBuildTool on Mac? - u_build_tool = "" - u_header_tool = "" + # we need to test this out + u_header_tool = (f"{engine_path}/Engine/Binaries/Mac/" + f"UnrealHeaderTool") + + if not u_header_tool: + raise NotImplemented("Unsupported platform") u_build_tool = u_build_tool.replace("\\", "/") u_header_tool = u_header_tool.replace("\\", "/") diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index f084cccfc3..7c4b6c3088 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +"""Hook to launch Unreal and prepare projects.""" import os from openpype.lib import ( @@ -8,19 +10,21 @@ from openpype.hosts.unreal.api import lib as unreal_lib class UnrealPrelaunchHook(PreLaunchHook): - """ + """Hook to handle launching Unreal. + This hook will check if current workfile path has Unreal project inside. IF not, it initialize it and finally it pass path to the project by environment variable to Unreal launcher shell script. - """ + """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.signature = "( {} )".format(self.__class__.__name__) def execute(self): + """Hook entry method.""" asset_name = self.data["asset_name"] task_name = self.data["task_name"] workdir = self.launch_context.env["AVALON_WORKDIR"] @@ -52,7 +56,7 @@ class UnrealPrelaunchHook(PreLaunchHook): f"[ {engine_version} ]" )) - detected = unreal_lib.get_engine_versions() + detected = unreal_lib.get_engine_versions(self.launch_context.env) detected_str = ', '.join(detected.keys()) or 'none' self.log.info(( f"{self.signature} detected UE4 versions: " From 969c32abd60fe66bebe04a5664aae1b79fd80187 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 9 Jun 2021 18:24:38 +0200 Subject: [PATCH 002/110] fix exceptions --- openpype/hosts/unreal/api/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 3efc0c63be..269ba561c7 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -174,7 +174,7 @@ def create_unreal_project(project_name: str, env (dict, optional): Environment to use. If not set, `os.environ`. Throws: - NotImplemented: For unsupported platforms. + NotImplementedError: For unsupported platforms. Returns: None @@ -304,7 +304,7 @@ def create_unreal_project(project_name: str, subprocess.run([python_path, "-m", "pip", "install", "pyside"]) else: - raise NotImplemented("Unsupported platform") + raise NotImplementedError("Unsupported platform") if dev_mode or preset["dev_mode"]: _prepare_cpp_project(project_file, engine_path) @@ -452,7 +452,7 @@ class {1}_API A{0}GameModeBase : public AGameModeBase f"UnrealHeaderTool") if not u_header_tool: - raise NotImplemented("Unsupported platform") + raise NotImplementedError("Unsupported platform") u_build_tool = u_build_tool.replace("\\", "/") u_header_tool = u_header_tool.replace("\\", "/") From 00336a9d2d6fc0bd9a16a0b9dfb44a0bfe4af641 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 9 Jun 2021 18:27:02 +0200 Subject: [PATCH 003/110] refactor ifs --- openpype/hosts/unreal/api/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 269ba561c7..e069ac5256 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -59,12 +59,12 @@ def get_engine_versions(env=None): # else kick in platform specific detection if platform.system().lower() == "windows": return OrderedDict(sorted(_win_get_engine_versions().items())) - elif platform.system().lower() == "linux": + if platform.system().lower() == "linux": # on linux, there is no installation and getting Unreal Engine involves # git clone. So we'll probably depend on `UNREAL_ENGINE_LOCATION`. pass - elif platform.system().lower() == "darwin": - return OrderedDict(sorted(_darwin_get_engine_version(env).items())) + if platform.system().lower() == "darwin": + return OrderedDict(sorted(_darwin_get_engine_version().items())) return OrderedDict() From b5900787079dd0f62c9a20911458a5d48ee1be6f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 16 Jun 2021 12:37:36 +0200 Subject: [PATCH 004/110] add PySide2 installation --- openpype/hosts/unreal/api/lib.py | 54 +++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index e069ac5256..56f92088b3 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -182,6 +182,36 @@ def create_unreal_project(project_name: str, """ env = env or os.environ preset = get_project_settings(project_name)["unreal"]["project_setup"] + ue_id = ".".join(ue_version.split(".")[:2]) + # get unreal engine identifier + # ------------------------------------------------------------------------- + # FIXME (antirotor): As of 4.26 this is problem with UE4 built from + # sources. In that case Engine ID is calculated per machine/user and not + # from Engine files as this code then reads. This then prevents UE4 + # to directly open project as it will complain about project being + # created in different UE4 version. When user convert such project + # to his UE4 version, Engine ID is replaced in uproject file. If some + # other user tries to open it, it will present him with similar error. + if platform.system().lower() == "windows": + ue4_modules = os.path.join(engine_path, "Engine", "Binaries", + "Win64", "UE4Editor.modules") + + if platform.system().lower() == "linux": + ue4_modules = os.path.join(engine_path, "Engine", "Binaries", + "Linux", "UE4Editor.modules") + + if platform.system().lower() == "darwin": + ue4_modules = os.path.join(engine_path, "Engine", "Binaries", + "Mac", "UE4Editor.modules") + + if os.path.exists(ue4_modules): + print("--- Loading Engine ID from modules file ...") + with open(ue4_modules, "r") as mp: + loaded_modules = json.load(mp) + + if loaded_modules.get("BuildId"): + ue_id = "{" + loaded_modules.get("BuildId") + "}" + plugins_path = None uep_path = None @@ -202,7 +232,7 @@ def create_unreal_project(project_name: str, # data for project file data = { "FileVersion": 3, - "EngineAssociation": ue_version, + "EngineAssociation": ue_id, "Category": "", "Description": "", "Plugins": [ @@ -305,6 +335,28 @@ def create_unreal_project(project_name: str, "pip", "install", "pyside"]) else: raise NotImplementedError("Unsupported platform") + else: + # install PySide2 inside newer engines + if platform.system().lower() == "windows": + python_path = os.path.join(engine_path, "Engine", "Binaries", + "ThirdParty", "Python3", "Win64", + "python3.exe") + + if platform.system().lower() == "linux": + python_path = os.path.join(engine_path, "Engine", "Binaries", + "ThirdParty", "Python3", "Linux", + "bin", "python3") + + if platform.system().lower() == "darwin": + python_path = os.path.join(engine_path, "Engine", "Binaries", + "ThirdParty", "Python3", "Mac", + "bin", "python3") + + if python_path: + subprocess.run([python_path, "-m", + "pip", "install", "pyside2"]) + else: + raise NotImplementedError("Unsupported platform") if dev_mode or preset["dev_mode"]: _prepare_cpp_project(project_file, engine_path) From cdbfe669c8df6b777e38cff15c71549d3d34c175 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 17 Jun 2021 13:47:23 +0200 Subject: [PATCH 005/110] raise exceptions when ue not found --- openpype/hosts/unreal/hooks/pre_workfile_preparation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 7c4b6c3088..c292730fb1 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -4,7 +4,8 @@ import os from openpype.lib import ( PreLaunchHook, - ApplicationLaunchFailed + ApplicationLaunchFailed, + ApplicationNotFound ) from openpype.hosts.unreal.api import lib as unreal_lib @@ -62,6 +63,8 @@ class UnrealPrelaunchHook(PreLaunchHook): f"{self.signature} detected UE4 versions: " f"[ {detected_str} ]" )) + if not detected: + raise ApplicationNotFound("No Unreal Engines are found.") engine_version = ".".join(engine_version.split(".")[:2]) if engine_version not in detected.keys(): From b1e716d5cfeb7600f21287b48d57168a5b308775 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 22 Jun 2021 12:19:41 +0200 Subject: [PATCH 006/110] StandalonePublisher: failing collector for editorial --- .../standalonepublisher/plugins/publish/collect_instances.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py index eb04217136..ad89abba63 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py @@ -55,7 +55,7 @@ class CollectInstances(pyblish.api.InstancePlugin): fps = plib.get_asset()["data"]["fps"] tracks = timeline.each_child( - descended_from_type=otio.schema.track.Track + descended_from_type=otio.schema.Track ) # get data from avalon @@ -92,7 +92,7 @@ class CollectInstances(pyblish.api.InstancePlugin): # Transitions are ignored, because Clips have the full frame # range. - if isinstance(clip, otio.schema.transition.Transition): + if isinstance(clip, otio.schema.Transition): continue # basic unique asset name From 39076fac360e4906173384cbb884e63a70465a05 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 22 Jun 2021 16:48:43 +0200 Subject: [PATCH 007/110] use pathlib, executable fixes --- openpype/hosts/unreal/api/lib.py | 173 +++++++++--------- .../unreal/hooks/pre_workfile_preparation.py | 29 ++- 2 files changed, 103 insertions(+), 99 deletions(-) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 56f92088b3..4760d1a78f 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -7,6 +7,7 @@ import json from distutils import dir_util import subprocess import re +from pathlib import Path from collections import OrderedDict from openpype.api import get_project_settings @@ -147,8 +148,8 @@ def _parse_launcher_locations(install_json_path: str) -> dict: def create_unreal_project(project_name: str, ue_version: str, - pr_dir: str, - engine_path: str, + pr_dir: Path, + engine_path: Path, dev_mode: bool = False, env: dict = None) -> None: """This will create `.uproject` file at specified location. @@ -162,8 +163,8 @@ def create_unreal_project(project_name: str, Args: project_name (str): Name of the project. ue_version (str): Unreal engine version (like 4.23). - pr_dir (str): Path to directory where project will be created. - engine_path (str): Path to Unreal Engine installation. + pr_dir (Path): Path to directory where project will be created. + engine_path (Path): Path to Unreal Engine installation. dev_mode (bool, optional): Flag to trigger C++ style Unreal project needing Visual Studio and other tools to compile plugins from sources. This will trigger automatically if `Binaries` @@ -192,19 +193,20 @@ def create_unreal_project(project_name: str, # created in different UE4 version. When user convert such project # to his UE4 version, Engine ID is replaced in uproject file. If some # other user tries to open it, it will present him with similar error. + ue4_modules = Path() if platform.system().lower() == "windows": - ue4_modules = os.path.join(engine_path, "Engine", "Binaries", - "Win64", "UE4Editor.modules") + ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries", + "Win64", "UE4Editor.modules")) if platform.system().lower() == "linux": - ue4_modules = os.path.join(engine_path, "Engine", "Binaries", - "Linux", "UE4Editor.modules") + ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries", + "Linux", "UE4Editor.modules")) if platform.system().lower() == "darwin": - ue4_modules = os.path.join(engine_path, "Engine", "Binaries", - "Mac", "UE4Editor.modules") + ue4_modules = Path(os.path.join(engine_path, "Engine", "Binaries", + "Mac", "UE4Editor.modules")) - if os.path.exists(ue4_modules): + if ue4_modules.exists(): print("--- Loading Engine ID from modules file ...") with open(ue4_modules, "r") as mp: loaded_modules = json.load(mp) @@ -217,16 +219,16 @@ def create_unreal_project(project_name: str, if os.path.isdir(env.get("AVALON_UNREAL_PLUGIN", "")): # copy plugin to correct path under project - plugins_path = os.path.join(pr_dir, "Plugins") - avalon_plugin_path = os.path.join(plugins_path, "Avalon") - if not os.path.isdir(avalon_plugin_path): - os.makedirs(avalon_plugin_path, exist_ok=True) + plugins_path = pr_dir / "Plugins" + avalon_plugin_path = plugins_path / "Avalon" + if not avalon_plugin_path.is_dir(): + avalon_plugin_path.mkdir(parents=True, exist_ok=True) dir_util._path_created = {} dir_util.copy_tree(os.environ.get("AVALON_UNREAL_PLUGIN"), - avalon_plugin_path) + avalon_plugin_path.as_posix()) - if (not os.path.isdir(os.path.join(avalon_plugin_path, "Binaries")) - or not os.path.join(avalon_plugin_path, "Intermediate")): + if not (avalon_plugin_path / "Binaries").is_dir() \ + or not (avalon_plugin_path / "Intermediate").is_dir(): dev_mode = True # data for project file @@ -247,14 +249,14 @@ def create_unreal_project(project_name: str, # support offline installation. # Otherwise clone UnrealEnginePython to Plugins directory # https://github.com/20tab/UnrealEnginePython.git - uep_path = os.path.join(plugins_path, "UnrealEnginePython") + uep_path = plugins_path / "UnrealEnginePython" if env.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"): os.makedirs(uep_path, exist_ok=True) dir_util._path_created = {} dir_util.copy_tree( env.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"), - uep_path) + uep_path.as_posix()) else: # WARNING: this will trigger dev_mode, because we need to compile # this plugin. @@ -262,13 +264,13 @@ def create_unreal_project(project_name: str, import git git.Repo.clone_from( "https://github.com/20tab/UnrealEnginePython.git", - uep_path) + uep_path.as_posix()) data["Plugins"].append( {"Name": "UnrealEnginePython", "Enabled": True}) - if (not os.path.isdir(os.path.join(uep_path, "Binaries")) - or not os.path.join(uep_path, "Intermediate")): + if not (uep_path / "Binaries").is_dir() \ + or not (uep_path / "Intermediate").is_dir(): dev_mode = True if dev_mode or preset["dev_mode"]: @@ -287,10 +289,8 @@ def create_unreal_project(project_name: str, # now we need to fix python path in: # `UnrealEnginePython.Build.cs` # to point to our python - with open(os.path.join( - uep_path, "Source", - "UnrealEnginePython", - "UnrealEnginePython.Build.cs"), mode="r") as f: + with open(uep_path / "Source" / "UnrealEnginePython" / + "UnrealEnginePython.Build.cs", mode="r") as f: build_file = f.read() fix = build_file.replace( @@ -298,14 +298,12 @@ def create_unreal_project(project_name: str, 'private string pythonHome = "{}";'.format( sys.base_prefix.replace("\\", "/"))) - with open(os.path.join( - uep_path, "Source", - "UnrealEnginePython", - "UnrealEnginePython.Build.cs"), mode="w") as f: + with open(uep_path / "Source" / "UnrealEnginePython" / + "UnrealEnginePython.Build.cs", mode="w") as f: f.write(fix) # write project file - project_file = os.path.join(pr_dir, "{}.uproject".format(project_name)) + project_file = pr_dir / f"{project_name}.uproject" with open(project_file, mode="w") as pf: json.dump(data, pf, indent=4) @@ -316,44 +314,43 @@ def create_unreal_project(project_name: str, if int(ue_version.split(".")[0]) == 4 and \ int(ue_version.split(".")[1]) < 25: if platform.system().lower() == "windows": - python_path = os.path.join(engine_path, "Engine", "Binaries", - "ThirdParty", "Python", "Win64", - "python.exe") + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python/Win64/python.exe") if platform.system().lower() == "linux": - python_path = os.path.join(engine_path, "Engine", "Binaries", - "ThirdParty", "Python", "Linux", - "bin", "python") + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python/Linux/bin/python") if platform.system().lower() == "darwin": - python_path = os.path.join(engine_path, "Engine", "Binaries", - "ThirdParty", "Python", "Mac", - "bin", "python") + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python/Mac/bin/python") - if python_path: - subprocess.run([python_path, "-m", + if python_path.exists(): + subprocess.run([python_path.as_posix(), "-m", "pip", "install", "pyside"]) else: raise NotImplementedError("Unsupported platform") else: # install PySide2 inside newer engines if platform.system().lower() == "windows": - python_path = os.path.join(engine_path, "Engine", "Binaries", - "ThirdParty", "Python3", "Win64", - "python3.exe") + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python3/Win64/pythonw.exe") if platform.system().lower() == "linux": - python_path = os.path.join(engine_path, "Engine", "Binaries", - "ThirdParty", "Python3", "Linux", - "bin", "python3") + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python3/Linux" / + "bin" / "python3") if platform.system().lower() == "darwin": - python_path = os.path.join(engine_path, "Engine", "Binaries", - "ThirdParty", "Python3", "Mac", - "bin", "python3") + python_path = (engine_path / "Engine" / "Binaries" / + "ThirdParty" / "Python3" / "Mac" / + "bin" / "python3") if python_path: - subprocess.run([python_path, "-m", + if not python_path.exists(): + raise RuntimeError( + f"Unreal Python not found at {python_path}") + subprocess.run([python_path.as_posix(), "-m", "pip", "install", "pyside2"]) else: raise NotImplementedError("Unsupported platform") @@ -362,7 +359,7 @@ def create_unreal_project(project_name: str, _prepare_cpp_project(project_file, engine_path) -def _prepare_cpp_project(project_file: str, engine_path: str) -> None: +def _prepare_cpp_project(project_file: Path, engine_path: Path) -> None: """Prepare CPP Unreal Project. This function will add source files needed for project to be @@ -379,13 +376,13 @@ def _prepare_cpp_project(project_file: str, engine_path: str) -> None: engine_path (str): Path to unreal engine associated with project. """ - project_name = os.path.splitext(os.path.basename(project_file))[0] - project_dir = os.path.dirname(project_file) - targets_dir = os.path.join(project_dir, "Source") - sources_dir = os.path.join(targets_dir, project_name) + project_name = project_file.stem + project_dir = project_file.parent + targets_dir = project_dir / "Source" + sources_dir = targets_dir / project_name - os.makedirs(sources_dir, exist_ok=True) - os.makedirs(os.path.join(project_dir, "Content"), exist_ok=True) + sources_dir.mkdir(parents=True, exist_ok=True) + (project_dir / "Content").mkdir(parents=True, exist_ok=True) module_target = ''' using UnrealBuildTool; @@ -460,63 +457,59 @@ class {1}_API A{0}GameModeBase : public AGameModeBase }}; '''.format(project_name, project_name.upper()) - with open(os.path.join( - targets_dir, f"{project_name}.Target.cs"), mode="w") as f: + with open(targets_dir / f"{project_name}.Target.cs", mode="w") as f: f.write(module_target) - with open(os.path.join( - targets_dir, f"{project_name}Editor.Target.cs"), mode="w") as f: + with open(targets_dir / f"{project_name}Editor.Target.cs", mode="w") as f: f.write(editor_module_target) - with open(os.path.join( - sources_dir, f"{project_name}.Build.cs"), mode="w") as f: + with open(sources_dir / f"{project_name}.Build.cs", mode="w") as f: f.write(module_build) - with open(os.path.join( - sources_dir, f"{project_name}.cpp"), mode="w") as f: + with open(sources_dir / f"{project_name}.cpp", mode="w") as f: f.write(module_cpp) - with open(os.path.join( - sources_dir, f"{project_name}.h"), mode="w") as f: + with open(sources_dir / f"{project_name}.h", mode="w") as f: f.write(module_header) - with open(os.path.join( - sources_dir, f"{project_name}GameModeBase.cpp"), mode="w") as f: + with open(sources_dir / f"{project_name}GameModeBase.cpp", mode="w") as f: f.write(game_mode_cpp) - with open(os.path.join( - sources_dir, f"{project_name}GameModeBase.h"), mode="w") as f: + with open(sources_dir / f"{project_name}GameModeBase.h", mode="w") as f: f.write(game_mode_h) - u_build_tool = (f"{engine_path}/Engine/Binaries/DotNET/" - "UnrealBuildTool.exe") + u_build_tool = Path( + engine_path / "Engine/Binaries/DotNET/UnrealBuildTool.exe") u_header_tool = None + arch = "Win64" if platform.system().lower() == "windows": - u_header_tool = (f"{engine_path}/Engine/Binaries/Win64/" - f"UnrealHeaderTool.exe") + arch = "Win64" + u_header_tool = Path( + engine_path / "Engine/Binaries/Win64/UnrealHeaderTool.exe") elif platform.system().lower() == "linux": - u_header_tool = (f"{engine_path}/Engine/Binaries/Linux/" - f"UnrealHeaderTool") + arch = "Linux" + u_header_tool = Path( + engine_path / "Engine/Binaries/Linux/UnrealHeaderTool") elif platform.system().lower() == "darwin": # we need to test this out - u_header_tool = (f"{engine_path}/Engine/Binaries/Mac/" - f"UnrealHeaderTool") + arch = "Mac" + u_header_tool = Path( + engine_path / "Engine/Binaries/Mac/UnrealHeaderTool") if not u_header_tool: raise NotImplementedError("Unsupported platform") - u_build_tool = u_build_tool.replace("\\", "/") - u_header_tool = u_header_tool.replace("\\", "/") - - command1 = [u_build_tool, "-projectfiles", f"-project={project_file}", - "-progress"] + command1 = [u_build_tool.as_posix(), "-projectfiles", + f"-project={project_file}", "-progress"] subprocess.run(command1) - command2 = [u_build_tool, f"-ModuleWithSuffix={project_name},3555" - "Win64", "Development", "-TargetType=Editor" - f'-Project="{project_file}"', f'"{project_file}"' + command2 = [u_build_tool.as_posix(), + f"-ModuleWithSuffix={project_name},3555", arch, + "Development", "-TargetType=Editor", + f'-Project={project_file}', + f'{project_file}', "-IgnoreJunk"] subprocess.run(command2) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index c292730fb1..2c4dd822bc 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- """Hook to launch Unreal and prepare projects.""" import os +from pathlib import Path +import platform from openpype.lib import ( PreLaunchHook, @@ -50,7 +52,7 @@ class UnrealPrelaunchHook(PreLaunchHook): )) unreal_project_name = f"P{unreal_project_name}" - project_path = os.path.join(workdir, unreal_project_name) + project_path = Path(os.path.join(workdir, unreal_project_name)) self.log.info(( f"{self.signature} requested UE4 version: " @@ -73,13 +75,21 @@ class UnrealPrelaunchHook(PreLaunchHook): f"detected [ {engine_version} ]" )) - os.makedirs(project_path, exist_ok=True) + ue4_path = Path(detected[engine_version]) / "Engine/Binaries" + if platform.system().lower() == "windows": + ue4_path = ue4_path / "Win64/UE4Editor.exe" - project_file = os.path.join( - project_path, - f"{unreal_project_name}.uproject" - ) - if not os.path.isfile(project_file): + elif platform.system().lower() == "linux": + ue4_path = ue4_path / "Linux/UE4Editor" + + elif platform.system().lower() == "darwin": + ue4_path = ue4_path / "Mac/UE4Editor" + + self.launch_context.launch_args.append(ue4_path.as_posix()) + project_path.mkdir(parents=True, exist_ok=True) + + project_file = project_path / f"{unreal_project_name}.uproject" + if not project_file.is_file(): engine_path = detected[engine_version] self.log.info(( f"{self.signature} creating unreal " @@ -95,8 +105,9 @@ class UnrealPrelaunchHook(PreLaunchHook): unreal_project_name, engine_version, project_path, - engine_path=engine_path + engine_path=Path(engine_path) ) # Append project file to launch arguments - self.launch_context.launch_args.append(f"\"{project_file}\"") + self.launch_context.launch_args.append( + f"\"{project_file.as_posix()}\"") From 3a2f29150b7b7f110c82990e9aa3e37b6793cc21 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 22 Jun 2021 16:53:15 +0200 Subject: [PATCH 008/110] fix indents --- openpype/hosts/unreal/api/lib.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 4760d1a78f..8f05a63273 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -338,13 +338,11 @@ def create_unreal_project(project_name: str, if platform.system().lower() == "linux": python_path = engine_path / ("Engine/Binaries/ThirdParty/" - "Python3/Linux" / - "bin" / "python3") + "Python3/Linux/bin/python3") if platform.system().lower() == "darwin": - python_path = (engine_path / "Engine" / "Binaries" / - "ThirdParty" / "Python3" / "Mac" / - "bin" / "python3") + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python3/Mac/bin/python3") if python_path: if not python_path.exists(): From c916d128bc05dcd2f99e01a1015901a875947226 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 24 Jun 2021 13:12:36 +0200 Subject: [PATCH 009/110] mark support for Unreal Python Engine deprecated --- openpype/hosts/unreal/api/lib.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 8f05a63273..6231fd6f33 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -245,17 +245,21 @@ def create_unreal_project(project_name: str, } if preset["install_unreal_python_engine"]: - # If `PYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from there to - # support offline installation. + # WARNING: This is deprecated as Unreal Engine Python project + # is on hold and is mainly replaced in 4.26 by Epics own + # Python implementation. + # --------------------------------------------------------------- + # If `OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from + # there to support offline installation. # Otherwise clone UnrealEnginePython to Plugins directory # https://github.com/20tab/UnrealEnginePython.git uep_path = plugins_path / "UnrealEnginePython" - if env.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"): + if env.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"): os.makedirs(uep_path, exist_ok=True) dir_util._path_created = {} dir_util.copy_tree( - env.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"), + env.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"), uep_path.as_posix()) else: # WARNING: this will trigger dev_mode, because we need to compile From fab1ed955313f810b26b47ba982bdf1d9c83e658 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 25 Jun 2021 13:19:16 +0200 Subject: [PATCH 010/110] move environment filling before mac specific check --- openpype/lib/applications.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index a7dcb6dd55..c8380dd3e0 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -449,6 +449,12 @@ class ApplicationExecutable: """Representation of executable loaded from settings.""" def __init__(self, executable): + # Try to format executable with environments + try: + executable = executable.format(**os.environ) + except Exception: + pass + # On MacOS check if exists path to executable when ends with `.app` # - it is common that path will lead to "/Applications/Blender" but # real path is "/Applications/Blender.app" @@ -460,12 +466,6 @@ class ApplicationExecutable: if os.path.exists(_executable): executable = _executable - # Try to format executable with environments - try: - executable = executable.format(**os.environ) - except Exception: - pass - self.executable_path = executable def __str__(self): From c69b20930c92281bc5b6bc2d4e821855f6612c0b Mon Sep 17 00:00:00 2001 From: Derek Severin Date: Sun, 27 Jun 2021 17:31:57 +0700 Subject: [PATCH 011/110] Minor doc fixes --- website/docs/dev_build.md | 4 ++-- website/docs/module_ftrack.md | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/website/docs/dev_build.md b/website/docs/dev_build.md index 2c4bd1e9af..b3e0c24fc2 100644 --- a/website/docs/dev_build.md +++ b/website/docs/dev_build.md @@ -137,12 +137,12 @@ $ pyenv install -v 3.7.10 $ cd /path/to/pype-3 # set local python version -$ pyenv local 3.7.9 +$ pyenv local 3.7.10 ``` :::note Install build requirements for **Ubuntu** ```shell -sudo apt-get update; sudo apt-get install --no-install-recommends make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev git +sudo apt-get update; sudo apt-get install --no-install-recommends make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev git patchelf ``` In case you run in error about `xcb` when running Pype, diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index bd0dbaef4f..9911dee45a 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -34,7 +34,7 @@ To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Ftrack Event Server is the key to automation of many tasks like _status change_, _thumbnail update_, _automatic synchronization to Avalon database_ and many more. Event server should run at all times to perform the required processing as it is not possible to catch some of them retrospectively with enough certainty. ### Running event server -There are specific launch arguments for event server. With `openpype eventserver` you can launch event server but without prior preparation it will terminate immediately. The reason is that event server requires 3 pieces of information: _Ftrack server url_, _paths to events_ and _credentials (Username and API key)_. Ftrack server URL and Event path are set from OpenPype's environments by default, but the credentials must be done separatelly for security reasons. +There are specific launch arguments for event server. With `openpype_console eventserver` you can launch event server but without prior preparation it will terminate immediately. The reason is that event server requires 3 pieces of information: _Ftrack server url_, _paths to events_ and _credentials (Username and API key)_. Ftrack server URL and Event path are set from OpenPype's environments by default, but the credentials must be done separatelly for security reasons. @@ -56,7 +56,7 @@ There are specific launch arguments for event server. With `openpype eventserver - `--ftrack-url "https://yourdomain.ftrackapp.com/"` : Ftrack server URL _(it is not needed to enter if you have set `FTRACK_SERVER` in OpenPype' environments)_ - `--ftrack-events-path "//Paths/To/Events/"` : Paths to events folder. May contain multiple paths separated by `;`. _(it is not needed to enter if you have set `FTRACK_EVENTS_PATH` in OpenPype' environments)_ -So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `openpype.exe eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `openpype.exe eventserver`. +So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `openpype_console.exe eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `openpype_console.exe eventserver`. @@ -100,14 +100,17 @@ Event server should **not** run more than once! It may cause major issues. - create file: - `sudo vi /opt/OpenPype/run_event_server.sh` + `sudo vi /opt/openpype/run_event_server.sh` - add content to the file: ```sh -#!\usr\bin\env +#!/usr/bin/env export OPENPYPE_DEBUG=3 pushd /mnt/pipeline/prod/openpype-setup -. openpype eventserver --ftrack-user --ftrack-api-key +. openpype_console eventserver --ftrack-user --ftrack-api-key ``` +- change file permission: + `sudo chmod 0755 /opt/openpype/run_event_server.sh` + - create service file: `sudo vi /etc/systemd/system/openpype-ftrack-event-server.service` - add content to the service file @@ -145,7 +148,7 @@ WantedBy=multi-user.target @echo off set OPENPYPE_DEBUG=3 pushd \\path\to\file\ -call openpype.bat eventserver --ftrack-user --ftrack-api-key +call openpype_console.exe eventserver --ftrack-user --ftrack-api-key ``` - download and install `nssm.cc` - create Windows service according to nssm.cc manual From c1d147934e8af6a600e8748f99b88a51d677d4b1 Mon Sep 17 00:00:00 2001 From: Derek Severin Date: Sun, 27 Jun 2021 17:55:16 +0700 Subject: [PATCH 012/110] Removed 'call' --- website/docs/module_ftrack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index 9911dee45a..6d56277c67 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -148,7 +148,7 @@ WantedBy=multi-user.target @echo off set OPENPYPE_DEBUG=3 pushd \\path\to\file\ -call openpype_console.exe eventserver --ftrack-user --ftrack-api-key +openpype_console.exe eventserver --ftrack-user --ftrack-api-key ``` - download and install `nssm.cc` - create Windows service according to nssm.cc manual From c98aafea8ef5f18bd31bdd53c13c52a299395b9b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:35:34 +0200 Subject: [PATCH 013/110] base of dict contitional --- .../settings/entities/dict_conditional.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 openpype/settings/entities/dict_conditional.py diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py new file mode 100644 index 0000000000..01da57a190 --- /dev/null +++ b/openpype/settings/entities/dict_conditional.py @@ -0,0 +1,33 @@ +import copy +import collections + +from .lib import ( + WRAPPER_TYPES, + OverrideState, + NOT_SET +) +from openpype.settings.constants import ( + METADATA_KEYS, + M_OVERRIDEN_KEY, + KEY_REGEX +) +from . import ( + BaseItemEntity, + ItemEntity, + BoolEntity, + GUIEntity +) +from .exceptions import ( + SchemaDuplicatedKeys, + EntitySchemaError, + InvalidKeySymbols +) + + +class DictConditionalEntity(ItemEntity): + schema_types = ["dict-conditional"] + _default_label_wrap = { + "use_label_wrap": False, + "collapsible": False, + "collapsed": True + } From 6c63bc048fe2e12f82581a14797dc8776d65f46e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:35:50 +0200 Subject: [PATCH 014/110] added example schema for reference --- .../settings/entities/dict_conditional.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 01da57a190..8e7a7b79c9 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -24,6 +24,52 @@ from .exceptions import ( ) +example_schema = { + "type": "dict-conditional", + "key": "KEY", + "label": "LABEL", + "enum_key": "type", + "enum_label": "label", + "enum_children": [ + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "command", + "label": "Comand" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "type": "list", + "object_type": "text" + } + ] + }, + { + "key": "separator", + "label": "Separator" + } + ] +} + + class DictConditionalEntity(ItemEntity): schema_types = ["dict-conditional"] _default_label_wrap = { From 1063f8210ab0ec1c8e7c41493408935023f88cda Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:36:38 +0200 Subject: [PATCH 015/110] implemented `_item_initalization` similar to 'dict' entity --- .../settings/entities/dict_conditional.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8e7a7b79c9..989d69a290 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -77,3 +77,33 @@ class DictConditionalEntity(ItemEntity): "collapsible": False, "collapsed": True } + + def _item_initalization(self): + self._default_metadata = NOT_SET + self._studio_override_metadata = NOT_SET + self._project_override_metadata = NOT_SET + + self._ignore_child_changes = False + + # `current_metadata` are still when schema is loaded + # - only metadata stored with dict item are gorup overrides in + # M_OVERRIDEN_KEY + self._current_metadata = {} + self._metadata_are_modified = False + + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (dict, ) + self.children = collections.defaultdict(list) + self.non_gui_children = collections.defaultdict(dict) + self.gui_layout = collections.defaultdict(list) + + if self.is_dynamic_item: + self.require_key = False + + self.enum_key = self.schema_data.get("enum_key") + self.enum_label = self.schema_data.get("enum_label") + self.enum_children = self.schema_data.get("enum_children") + + self.enum_entity = None + self.current_enum = None From 82f1817ec0ca4fca3cd6efca01b28e28c76b1715 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:39:32 +0200 Subject: [PATCH 016/110] implemented _add_children method --- .../settings/entities/dict_conditional.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 989d69a290..da6df6170d 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -107,3 +107,61 @@ class DictConditionalEntity(ItemEntity): self.enum_entity = None self.current_enum = None + + self._add_children() + + def _add_children(self): + """Add children from schema data and repare enum items. + + Each enum item must have defined it's children. None are shared across + all enum items. + + Nice to have: Have ability to have shared keys across all enum items. + + All children are stored by their enum item. + """ + # Skip and wait for validation + if not self.enum_children or not self.enum_key: + return + + enum_items = [] + valid_enum_items = [] + for item in self.enum_children: + if isinstance(item, dict) and "key" in item: + valid_enum_items.append(item) + + first_key = None + for item in valid_enum_items: + item_key = item["key"] + if first_key is None: + first_key = item_key + item_label = item.get("label") or item_key + enum_items.append({item_key: item_label}) + + if not enum_items: + return + + self.current_enum = first_key + + enum_key = self.enum_key or "invalid" + enum_schema = { + "type": "enum", + "multiselection": False, + "enum_items": enum_items, + "key": enum_key, + "label": self.enum_label or enum_key + } + enum_entity = self.create_schema_object(enum_schema, self) + self.enum_entity = enum_entity + + for item in valid_enum_items: + item_key = item["key"] + children = item.get("children") or [] + for children_schema in children: + child_obj = self.create_schema_object(children_schema, self) + self.children[item_key].append(child_obj) + self.gui_layout[item_key].append(child_obj) + if isinstance(child_obj, GUIEntity): + continue + + self.non_gui_children[item_key][child_obj.key] = child_obj From 694f6b58cb0467aa7ddd496a60a61ef220db31b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:39:54 +0200 Subject: [PATCH 017/110] added schema validations of conditional dictionary --- .../settings/entities/dict_conditional.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index da6df6170d..20ee80337d 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -110,6 +110,67 @@ class DictConditionalEntity(ItemEntity): self._add_children() + def schema_validations(self): + """Validation of schema data.""" + if self.enum_key is None: + raise EntitySchemaError(self, "Key 'enum_key' is not set.") + + if not isinstance(self.enum_children, list): + raise EntitySchemaError( + self, "Key 'enum_children' must be a list. Got: {}".format( + str(type(self.enum_children)) + ) + ) + + if not self.enum_children: + raise EntitySchemaError(self, ( + "Key 'enum_children' have empty value. Entity can't work" + " without children definitions." + )) + + children_def_keys = [] + for children_def in self.enum_children: + if not isinstance(children_def, dict): + raise EntitySchemaError(( + "Children definition under key 'enum_children' must" + " be a dictionary." + )) + + if "key" not in children_def: + raise EntitySchemaError(( + "Children definition under key 'enum_children' miss" + " 'key' definition." + )) + # We don't validate regex of these keys because they will be stored + # as value at the end. + key = children_def["key"] + if key in children_def_keys: + # TODO this hould probably be different exception? + raise SchemaDuplicatedKeys(self, key) + children_def_keys.append(key) + + for children in self.children.values(): + children_keys = set() + children_keys.add(self.enum_key) + for child_entity in children: + if not isinstance(child_entity, BaseItemEntity): + continue + elif child_entity.key not in children_keys: + children_keys.add(child_entity.key) + else: + raise SchemaDuplicatedKeys(self, child_entity.key) + + for children_by_key in self.non_gui_children.values(): + for key in children_by_key.keys(): + if not KEY_REGEX.match(key): + raise InvalidKeySymbols(self.path, key) + + super(DictConditionalEntity, self).schema_validations() + # Trigger schema validation on children entities + for children in self.children.values(): + for child_obj in children: + child_obj.schema_validations() + def _add_children(self): """Add children from schema data and repare enum items. From ff701860f704c186fe969d76042747bd227bb331 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:48:09 +0200 Subject: [PATCH 018/110] implemented `get_child_path` --- .../settings/entities/dict_conditional.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 20ee80337d..79b5624505 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -226,3 +226,24 @@ class DictConditionalEntity(ItemEntity): continue self.non_gui_children[item_key][child_obj.key] = child_obj + + def get_child_path(self, child_obj): + """Get hierarchical path of child entity. + + Child must be entity's direct children. This must be possible to get + for any children even if not from current enum value. + """ + if child_obj is self.enum_entity: + return "/".join([self.path, self.enum_key]) + + result_key = None + for children in self.non_gui_children.values(): + for key, _child_obj in children.items(): + if _child_obj is child_obj: + result_key = key + break + + if result_key is None: + raise ValueError("Didn't found child {}".format(child_obj)) + + return "/".join([self.path, result_key]) From 218158ddc4961e24788c332fb74104678452ed4d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:51:56 +0200 Subject: [PATCH 019/110] implemented base dictionary methods --- .../settings/entities/dict_conditional.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 79b5624505..71e727e53f 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -78,6 +78,52 @@ class DictConditionalEntity(ItemEntity): "collapsed": True } + def __getitem__(self, key): + """Return entity inder key.""" + return self.non_gui_children[self.current_enum][key] + + def __setitem__(self, key, value): + """Set value of item under key.""" + child_obj = self.non_gui_children[self.current_enum][key] + child_obj.set(value) + + def __iter__(self): + """Iter through keys.""" + for key in self.keys(): + yield key + + def __contains__(self, key): + """Check if key is available.""" + return key in self.non_gui_children[self.current_enum] + + def get(self, key, default=None): + """Safe entity getter by key.""" + return self.non_gui_children[self.current_enum].get(key, default) + + def keys(self): + """Entity's keys.""" + keys = list(self.non_gui_children[self.current_enum].keys()) + keys.insert(0, [self.current_enum]) + return keys + + def values(self): + """Children entities.""" + values = [ + self.enum_entity + ] + for child_entiy in self.non_gui_children[self.current_enum].values(): + values.append(child_entiy) + return values + + def items(self): + """Children entities paired with their key (key, value).""" + items = [ + (self.enum_key, self.enum_entity) + ] + for key, value in self.non_gui_children[self.current_enum].items(): + items.append((key, value)) + return items + def _item_initalization(self): self._default_metadata = NOT_SET self._studio_override_metadata = NOT_SET From 9ccc667c85471437c5acace23f2279b13e97380a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:53:45 +0200 Subject: [PATCH 020/110] implemented idea of set value --- openpype/settings/entities/dict_conditional.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 71e727e53f..a933dfd586 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -124,6 +124,16 @@ class DictConditionalEntity(ItemEntity): items.append((key, value)) return items + def set(self, value): + """Set value.""" + new_value = self.convert_to_valid_type(value) + # First change value of enum key if available + if self.enum_key in new_value: + self.enum_entity.set(new_value.pop(self.enum_key)) + + for _key, _value in new_value.items(): + self.non_gui_children[self.current_enum][_key].set(_value) + def _item_initalization(self): self._default_metadata = NOT_SET self._studio_override_metadata = NOT_SET From 469dcc09fa7051313039b81e7ff4a6ebcdf840aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:55:34 +0200 Subject: [PATCH 021/110] implemented change callbacks --- .../settings/entities/dict_conditional.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index a933dfd586..9e6b5b6f36 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -227,6 +227,25 @@ class DictConditionalEntity(ItemEntity): for child_obj in children: child_obj.schema_validations() + def on_change(self): + """Update metadata on change and pass change to parent.""" + self._update_current_metadata() + + for callback in self.on_change_callbacks: + callback() + self.parent.on_child_change(self) + + def on_child_change(self, child_obj): + """Trigger on change callback if child changes are not ignored.""" + if self._ignore_child_changes: + return + + if ( + child_obj is self.enum_entity + or child_obj in self.children[self.current_enum] + ): + self.on_change() + def _add_children(self): """Add children from schema data and repare enum items. From be1f0f77a05351bd7c3a154ad35e787a392701d3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:58:55 +0200 Subject: [PATCH 022/110] implemented set_override_state --- openpype/settings/entities/dict_conditional.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 9e6b5b6f36..8bf9b87218 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -322,3 +322,20 @@ class DictConditionalEntity(ItemEntity): raise ValueError("Didn't found child {}".format(child_obj)) return "/".join([self.path, result_key]) + + def set_override_state(self, state): + # Trigger override state change of root if is not same + if self.root_item.override_state is not state: + self.root_item.set_override_state(state) + return + + # Change has/had override states + self._override_state = state + + self.enum_entity.set_override_state(state) + + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.set_override_state(state) + + self._update_current_metadata() From 92d0c9f37b3e116700ff25a3422acfb3436aefe6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:02:05 +0200 Subject: [PATCH 023/110] implemented `value` and `settings_value` --- .../settings/entities/dict_conditional.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8bf9b87218..d2bab1ed15 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -339,3 +339,53 @@ class DictConditionalEntity(ItemEntity): child_obj.set_override_state(state) self._update_current_metadata() + + @property + def value(self): + output = { + self.current_enum: self.enum_entity.value + } + for key, child_obj in self.non_gui_children[self.current_enum].items(): + output[key] = child_obj.value + return output + + def settings_value(self): + if self._override_state is OverrideState.NOT_DEFINED: + return NOT_SET + + if self._override_state is OverrideState.DEFAULTS: + output = { + self.current_enum: self.enum_entity.settings_value() + } + non_gui_children = self.non_gui_children[self.current_enum] + for key, child_obj in non_gui_children.items(): + child_value = child_obj.settings_value() + if not child_obj.is_file and not child_obj.file_item: + for _key, _value in child_value.items(): + new_key = "/".join([key, _key]) + output[new_key] = _value + else: + output[key] = child_value + return output + + if self.is_group: + if self._override_state is OverrideState.STUDIO: + if not self.has_studio_override: + return NOT_SET + elif self._override_state is OverrideState.PROJECT: + if not self.has_project_override: + return NOT_SET + + output = { + self.current_enum: self.enum_entity.settings_value() + } + for key, child_obj in self.non_gui_children[self.current_enum].items(): + value = child_obj.settings_value() + if value is not NOT_SET: + output[key] = value + + if not output: + return NOT_SET + + output.update(self._current_metadata) + return output From 04207c689fe584c8c2393ad95893f67d6e5dc4b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:02:23 +0200 Subject: [PATCH 024/110] implemented modification and override properties --- .../settings/entities/dict_conditional.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index d2bab1ed15..f82cc02e3e 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -349,6 +349,52 @@ class DictConditionalEntity(ItemEntity): output[key] = child_obj.value return output + @property + def has_unsaved_changes(self): + if self._metadata_are_modified: + return True + + return self._child_has_unsaved_changes + + @property + def _child_has_unsaved_changes(self): + if self.enum_entity.has_unsaved_changes: + return True + for child_obj in self.non_gui_children[self.current_enum].values(): + if child_obj.has_unsaved_changes: + return True + return False + + @property + def has_studio_override(self): + return self._child_has_studio_override + + @property + def _child_has_studio_override(self): + if self._override_state >= OverrideState.STUDIO: + if self.enum_entity.has_studio_override: + return True + + for child_obj in self.non_gui_children[self.current_enum].values(): + if child_obj.has_studio_override: + return True + return False + + @property + def has_project_override(self): + return self._child_has_project_override + + @property + def _child_has_project_override(self): + if self._override_state >= OverrideState.PROJECT: + if self.enum_entity.has_project_override: + return True + + for child_obj in self.non_gui_children[self.current_enum].values(): + if child_obj.has_project_override: + return True + return False + def settings_value(self): if self._override_state is OverrideState.NOT_DEFINED: return NOT_SET From 58ade824c5a49f0f59fcd6ad0d46c24ded5bd7c1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:02:45 +0200 Subject: [PATCH 025/110] implemented update current metadata --- .../settings/entities/dict_conditional.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index f82cc02e3e..df7699a90e 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -323,6 +323,45 @@ class DictConditionalEntity(ItemEntity): return "/".join([self.path, result_key]) + def _update_current_metadata(self): + current_metadata = {} + for key, child_obj in self.non_gui_children[self.current_enum].items(): + if self._override_state is OverrideState.DEFAULTS: + break + + if not child_obj.is_group: + continue + + if ( + self._override_state is OverrideState.STUDIO + and not child_obj.has_studio_override + ): + continue + + if ( + self._override_state is OverrideState.PROJECT + and not child_obj.has_project_override + ): + continue + + if M_OVERRIDEN_KEY not in current_metadata: + current_metadata[M_OVERRIDEN_KEY] = [] + current_metadata[M_OVERRIDEN_KEY].append(key) + + # Define if current metadata are avaialble for current override state + metadata = NOT_SET + if self._override_state is OverrideState.STUDIO: + metadata = self._studio_override_metadata + + elif self._override_state is OverrideState.PROJECT: + metadata = self._project_override_metadata + + if metadata is NOT_SET: + metadata = {} + + self._metadata_are_modified = current_metadata != metadata + self._current_metadata = current_metadata + def set_override_state(self, state): # Trigger override state change of root if is not same if self.root_item.override_state is not state: From 1f9ba64a45bbe6b2300436b341b10e985597ca63 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:02:55 +0200 Subject: [PATCH 026/110] implemented prepare value --- .../settings/entities/dict_conditional.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index df7699a90e..8172550075 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -474,3 +474,37 @@ class DictConditionalEntity(ItemEntity): output.update(self._current_metadata) return output + + def _prepare_value(self, value): + if value is NOT_SET or self.enum_key not in value: + return NOT_SET, NOT_SET + + enum_value = value.get(self.enum_key) + if enum_value not in self.non_gui_children: + return NOT_SET, NOT_SET + + # Create copy of value before poping values + value = copy.deepcopy(value) + metadata = {} + for key in METADATA_KEYS: + if key in value: + metadata[key] = value.pop(key) + + enum_value = value.get(self.enum_key) + + old_metadata = metadata.get(M_OVERRIDEN_KEY) + if old_metadata: + old_metadata_set = set(old_metadata) + new_metadata = [] + non_gui_children = self.non_gui_children[enum_value] + for key in non_gui_children.keys(): + if key in old_metadata: + new_metadata.append(key) + old_metadata_set.remove(key) + + for key in old_metadata_set: + new_metadata.append(key) + metadata[M_OVERRIDEN_KEY] = new_metadata + + return value, metadata + From f47ec0df6cfe1137e78afa14b27c54b8e4f88e49 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:15:06 +0200 Subject: [PATCH 027/110] implemented update methods --- .../settings/entities/dict_conditional.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8172550075..20697506fd 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -508,3 +508,112 @@ class DictConditionalEntity(ItemEntity): return value, metadata + def update_default_value(self, value): + """Update default values. + + Not an api method, should be called by parent. + """ + value = self._check_update_value(value, "default") + self.has_default_value = value is not NOT_SET + # TODO add value validation + value, metadata = self._prepare_value(value) + self._default_metadata = metadata + + if value is NOT_SET: + self.enum_entity.update_default_value(value) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key: + child_obj.update_default_value(value) + return + + value_keys = set(value.keys()) + enum_value = value[self.enum_key] + expected_keys = set(self.non_gui_children[enum_value]) + expected_keys.add(self.enum_key) + unknown_keys = value_keys - expected_keys + if unknown_keys: + self.log.warning( + "{} Unknown keys in default values: {}".format( + self.path, + ", ".join("\"{}\"".format(key) for key in unknown_keys) + ) + ) + + self.enum_entity.update_default_value(enum_value) + for children_by_key in self.non_gui_children.items(): + for key, child_obj in children_by_key.items(): + child_value = value.get(key, NOT_SET) + child_obj.update_default_value(child_value) + + def update_studio_value(self, value): + """Update studio override values. + + Not an api method, should be called by parent. + """ + value = self._check_update_value(value, "studio override") + value, metadata = self._prepare_value(value) + self._studio_override_metadata = metadata + self.had_studio_override = metadata is not NOT_SET + + if value is NOT_SET: + self.enum_entity.update_default_value(value) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key: + child_obj.update_default_value(value) + return + + value_keys = set(value.keys()) + enum_value = value[self.enum_key] + expected_keys = set(self.non_gui_children[enum_value]) + expected_keys.add(self.enum_key) + unknown_keys = value_keys - expected_keys + if unknown_keys: + self.log.warning( + "{} Unknown keys in studio overrides: {}".format( + self.path, + ", ".join("\"{}\"".format(key) for key in unknown_keys) + ) + ) + + self.enum_entity.update_studio_value(enum_value) + for children_by_key in self.non_gui_children.items(): + for key, child_obj in children_by_key.items(): + child_value = value.get(key, NOT_SET) + child_obj.update_studio_value(child_value) + + def update_project_value(self, value): + """Update project override values. + + Not an api method, should be called by parent. + """ + value = self._check_update_value(value, "project override") + value, metadata = self._prepare_value(value) + self._project_override_metadata = metadata + self.had_project_override = metadata is not NOT_SET + + if value is NOT_SET: + self.enum_entity.update_default_value(value) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key: + child_obj.update_default_value(value) + return + + value_keys = set(value.keys()) + enum_value = value[self.enum_key] + expected_keys = set(self.non_gui_children[enum_value]) + expected_keys.add(self.enum_key) + unknown_keys = value_keys - expected_keys + if unknown_keys: + self.log.warning( + "{} Unknown keys in project overrides: {}".format( + self.path, + ", ".join("\"{}\"".format(key) for key in unknown_keys) + ) + ) + + self.enum_entity.update_project_value(enum_value) + for children_by_key in self.non_gui_children.items(): + for key, child_obj in children_by_key.items(): + child_value = value.get(key, NOT_SET) + child_obj.update_project_value(child_value) + From baf706627af1cfc6a4724897d3ddfda054bcac37 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:15:28 +0200 Subject: [PATCH 028/110] implemented actions for conditional dictionary --- .../settings/entities/dict_conditional.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 20697506fd..5549ce13f2 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -617,3 +617,64 @@ class DictConditionalEntity(ItemEntity): child_value = value.get(key, NOT_SET) child_obj.update_project_value(child_value) + def _discard_changes(self, on_change_trigger): + self._ignore_child_changes = True + + self.enum_entity.discard_changes(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.discard_changes(on_change_trigger) + + self._ignore_child_changes = False + + def _add_to_studio_default(self, on_change_trigger): + self._ignore_child_changes = True + + self.enum_entity.add_to_studio_default(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.add_to_studio_default(on_change_trigger) + + self._ignore_child_changes = False + + self._update_current_metadata() + + self.parent.on_child_change(self) + + def _remove_from_studio_default(self, on_change_trigger): + self._ignore_child_changes = True + + self.enum_entity.remove_from_studio_default(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.remove_from_studio_default(on_change_trigger) + + self._ignore_child_changes = False + + def _add_to_project_override(self, on_change_trigger): + self._ignore_child_changes = True + + self.enum_entity.add_to_project_override(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.add_to_project_override(on_change_trigger) + + self._ignore_child_changes = False + + self._update_current_metadata() + + self.parent.on_child_change(self) + + def _remove_from_project_override(self, on_change_trigger): + if self._override_state is not OverrideState.PROJECT: + return + + self._ignore_child_changes = True + + self.enum_entity.remove_from_project_override(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.remove_from_project_override(on_change_trigger) + + self._ignore_child_changes = False + From f70b19305cd69f4d9755324b97dfc6702970b290 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:16:07 +0200 Subject: [PATCH 029/110] implemented reset_callbacks method --- openpype/settings/entities/dict_conditional.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 5549ce13f2..df852587f6 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -678,3 +678,9 @@ class DictConditionalEntity(ItemEntity): self._ignore_child_changes = False + def reset_callbacks(self): + """Reset registered callbacks on entity and children.""" + super(DictConditionalEntity, self).reset_callbacks() + for children in self.children.values(): + for child_entity in children: + child_entity.reset_callbacks() From 1048a1268cde84d2d0236c78d6eacc7d5f2a25d1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:18:02 +0200 Subject: [PATCH 030/110] import DictConditionalEntity in entities init --- openpype/settings/entities/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index 94eb819f2b..c0eef15e69 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -111,6 +111,7 @@ from .enum_entity import ( from .list_entity import ListEntity from .dict_immutable_keys_entity import DictImmutableKeysEntity from .dict_mutable_keys_entity import DictMutableKeysEntity +from .dict_conditional import DictConditionalEntity from .anatomy_entities import AnatomyEntity @@ -166,5 +167,7 @@ __all__ = ( "DictMutableKeysEntity", + "DictConditionalEntity", + "AnatomyEntity" ) From e8f7f1418e6a11c510825f7337388b2909b77214 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:32:35 +0200 Subject: [PATCH 031/110] fix example schema --- openpype/settings/entities/dict_conditional.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index df852587f6..e5803b7606 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -57,6 +57,8 @@ example_schema = { "label": "Menu", "children": [ { + "key": "children", + "label": "Children", "type": "list", "object_type": "text" } From 85a3dd1ea6d6742df6eb23d4a8609db3a892c8d4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:32:54 +0200 Subject: [PATCH 032/110] fix dict vs. list approach --- openpype/settings/entities/dict_conditional.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index e5803b7606..8fc22348a7 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -524,7 +524,7 @@ class DictConditionalEntity(ItemEntity): if value is NOT_SET: self.enum_entity.update_default_value(value) for children_by_key in self.non_gui_children.values(): - for child_obj in children_by_key: + for child_obj in children_by_key.values(): child_obj.update_default_value(value) return @@ -560,7 +560,7 @@ class DictConditionalEntity(ItemEntity): if value is NOT_SET: self.enum_entity.update_default_value(value) for children_by_key in self.non_gui_children.values(): - for child_obj in children_by_key: + for child_obj in children_by_key.values(): child_obj.update_default_value(value) return @@ -596,7 +596,7 @@ class DictConditionalEntity(ItemEntity): if value is NOT_SET: self.enum_entity.update_default_value(value) for children_by_key in self.non_gui_children.values(): - for child_obj in children_by_key: + for child_obj in children_by_key.values(): child_obj.update_default_value(value) return From 16ac770359d5d9d78e9db11fde41649838e922e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 19:57:42 +0200 Subject: [PATCH 033/110] created copy of DictConditionalWidget as base for DictConditionalWidget --- .../tools/settings/settings/categories.py | 5 + .../settings/settings/dict_conditional.py | 253 ++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 openpype/tools/settings/settings/dict_conditional.py diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 34ab4c464a..0dfafce186 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -11,6 +11,7 @@ from openpype.settings.entities import ( GUIEntity, DictImmutableKeysEntity, DictMutableKeysEntity, + DictConditionalEntity, ListEntity, PathEntity, ListStrictEntity, @@ -35,6 +36,7 @@ from .base import GUIWidget from .list_item_widget import ListWidget from .list_strict_widget import ListStrictWidget from .dict_mutable_widget import DictMutableKeysWidget +from .dict_conditional import DictConditionalWidget from .item_widgets import ( BoolWidget, DictImmutableKeysWidget, @@ -100,6 +102,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget): if isinstance(entity, GUIEntity): return GUIWidget(*args) + elif isinstance(entity, DictConditionalEntity): + return DictConditionalWidget(*args) + elif isinstance(entity, DictImmutableKeysEntity): return DictImmutableKeysWidget(*args) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py new file mode 100644 index 0000000000..e7e0a31401 --- /dev/null +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -0,0 +1,253 @@ +import collections +from Qt import QtWidgets, QtCore, QtGui + +from .widgets import ( + ExpandingWidget, + GridLabelWidget +) +from .wrapper_widgets import ( + WrapperWidget, + CollapsibleWrapper, + FormWrapper +) +from .base import BaseWidget +from openpype.tools.settings import CHILD_OFFSET + + +class DictConditionalWidget(BaseWidget): + def create_ui(self): + self.input_fields = [] + self.checkbox_child = None + + self.label_widget = None + self.body_widget = None + self.content_widget = None + self.content_layout = None + + label = None + if self.entity.is_dynamic_item: + self._ui_as_dynamic_item() + + elif self.entity.use_label_wrap: + self._ui_label_wrap() + self.checkbox_child = self.entity.non_gui_children.get( + self.entity.checkbox_key + ) + + else: + self._ui_item_base() + label = self.entity.label + + self._parent_widget_by_entity_id = {} + self._added_wrapper_ids = set() + self._prepare_entity_layouts( + self.entity.gui_layout, self.content_widget + ) + + for child_obj in self.entity.children: + self.input_fields.append( + self.create_ui_for_entity( + self.category_widget, child_obj, self + ) + ) + + if self.entity.use_label_wrap and self.content_layout.count() == 0: + self.body_widget.hide_toolbox(True) + + self.entity_widget.add_widget_to_layout(self, label) + + def _prepare_entity_layouts(self, children, widget): + for child in children: + if not isinstance(child, dict): + if child is not self.checkbox_child: + self._parent_widget_by_entity_id[child.id] = widget + continue + + if child["type"] == "collapsible-wrap": + wrapper = CollapsibleWrapper(child, widget) + + elif child["type"] == "form": + wrapper = FormWrapper(child, widget) + + else: + raise KeyError( + "Unknown Wrapper type \"{}\"".format(child["type"]) + ) + + self._parent_widget_by_entity_id[wrapper.id] = widget + + self._prepare_entity_layouts(child["children"], wrapper) + + def _ui_item_base(self): + self.setObjectName("DictInvisible") + + self.content_widget = self + self.content_layout = QtWidgets.QGridLayout(self) + self.content_layout.setContentsMargins(0, 0, 0, 0) + self.content_layout.setSpacing(5) + + def _ui_as_dynamic_item(self): + content_widget = QtWidgets.QWidget(self) + content_widget.setObjectName("DictAsWidgetBody") + + show_borders = str(int(self.entity.show_borders)) + content_widget.setProperty("show_borders", show_borders) + + label_widget = QtWidgets.QLabel(self.entity.label) + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(5, 5, 5, 5) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(5) + main_layout.addWidget(content_widget) + + self.label_widget = label_widget + self.content_widget = content_widget + self.content_layout = content_layout + + def _ui_label_wrap(self): + content_widget = QtWidgets.QWidget(self) + content_widget.setObjectName("ContentWidget") + + if self.entity.highlight_content: + content_state = "hightlighted" + bottom_margin = 5 + else: + content_state = "" + bottom_margin = 0 + content_widget.setProperty("content_state", content_state) + content_layout_margins = (CHILD_OFFSET, 5, 0, bottom_margin) + + body_widget = ExpandingWidget(self.entity.label, self) + label_widget = body_widget.label_widget + body_widget.set_content_widget(content_widget) + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(*content_layout_margins) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(body_widget) + + self.label_widget = label_widget + self.body_widget = body_widget + self.content_widget = content_widget + self.content_layout = content_layout + + if len(self.input_fields) == 1 and self.checkbox_child: + body_widget.hide_toolbox(hide_content=True) + + elif self.entity.collapsible: + if not self.entity.collapsed: + body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) + + def add_widget_to_layout(self, widget, label=None): + if self.checkbox_child and widget.entity is self.checkbox_child: + self.body_widget.add_widget_before_label(widget) + return + + if not widget.entity: + map_id = widget.id + else: + map_id = widget.entity.id + + wrapper = self._parent_widget_by_entity_id[map_id] + if wrapper is not self.content_widget: + wrapper.add_widget_to_layout(widget, label) + if wrapper.id not in self._added_wrapper_ids: + self.add_widget_to_layout(wrapper) + self._added_wrapper_ids.add(wrapper.id) + return + + row = self.content_layout.rowCount() + if not label or isinstance(widget, WrapperWidget): + self.content_layout.addWidget(widget, row, 0, 1, 2) + else: + label_widget = GridLabelWidget(label, widget) + label_widget.input_field = widget + widget.label_widget = label_widget + self.content_layout.addWidget(label_widget, row, 0, 1, 1) + self.content_layout.addWidget(widget, row, 1, 1, 1) + + def set_entity_value(self): + for input_field in self.input_fields: + input_field.set_entity_value() + + def hierarchical_style_update(self): + self.update_style() + for input_field in self.input_fields: + input_field.hierarchical_style_update() + + def update_style(self): + if not self.body_widget and not self.label_widget: + return + + if self.entity.group_item: + group_item = self.entity.group_item + has_unsaved_changes = group_item.has_unsaved_changes + has_project_override = group_item.has_project_override + has_studio_override = group_item.has_studio_override + else: + has_unsaved_changes = self.entity.has_unsaved_changes + has_project_override = self.entity.has_project_override + has_studio_override = self.entity.has_studio_override + + style_state = self.get_style_state( + self.is_invalid, + has_unsaved_changes, + has_project_override, + has_studio_override + ) + if self._style_state == style_state: + return + + self._style_state = style_state + + if self.body_widget: + if style_state: + child_style_state = "child-{}".format(style_state) + else: + child_style_state = "" + + self.body_widget.side_line_widget.setProperty( + "state", child_style_state + ) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + + # There is nothing to care if there is no label + if not self.label_widget: + return + + # Don't change label if is not group or under group item + if not self.entity.is_group and not self.entity.group_item: + return + + self.label_widget.setProperty("state", style_state) + self.label_widget.style().polish(self.label_widget) + + def _on_entity_change(self): + pass + + @property + def is_invalid(self): + return self._is_invalid or self._child_invalid + + @property + def _child_invalid(self): + for input_field in self.input_fields: + if input_field.is_invalid: + return True + return False + + def get_invalid(self): + invalid = [] + for input_field in self.input_fields: + invalid.extend(input_field.get_invalid()) + return invalid From c3614dbfce046c692968399a9e7433613bff6655 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 19:58:29 +0200 Subject: [PATCH 034/110] removed checkbox checks as checkbox is not available for conditional dict --- .../tools/settings/settings/dict_conditional.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index e7e0a31401..2024bd3258 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -17,7 +17,6 @@ from openpype.tools.settings import CHILD_OFFSET class DictConditionalWidget(BaseWidget): def create_ui(self): self.input_fields = [] - self.checkbox_child = None self.label_widget = None self.body_widget = None @@ -30,9 +29,6 @@ class DictConditionalWidget(BaseWidget): elif self.entity.use_label_wrap: self._ui_label_wrap() - self.checkbox_child = self.entity.non_gui_children.get( - self.entity.checkbox_key - ) else: self._ui_item_base() @@ -59,8 +55,7 @@ class DictConditionalWidget(BaseWidget): def _prepare_entity_layouts(self, children, widget): for child in children: if not isinstance(child, dict): - if child is not self.checkbox_child: - self._parent_widget_by_entity_id[child.id] = widget + parent_widget_by_entity_id[child.id] = widget continue if child["type"] == "collapsible-wrap": @@ -137,20 +132,13 @@ class DictConditionalWidget(BaseWidget): self.content_widget = content_widget self.content_layout = content_layout - if len(self.input_fields) == 1 and self.checkbox_child: - body_widget.hide_toolbox(hide_content=True) - - elif self.entity.collapsible: + if self.entity.collapsible: if not self.entity.collapsed: body_widget.toggle_content() else: body_widget.hide_toolbox(hide_content=False) def add_widget_to_layout(self, widget, label=None): - if self.checkbox_child and widget.entity is self.checkbox_child: - self.body_widget.add_widget_before_label(widget) - return - if not widget.entity: map_id = widget.id else: From 1b1ce1f2a5602b07714f19e8aeb0404ba072f7f7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 19:59:07 +0200 Subject: [PATCH 035/110] modified _prepare_entity_layouts to be able to store result into passed dictionary --- openpype/tools/settings/settings/dict_conditional.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 2024bd3258..aeb2b7d86c 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -52,8 +52,10 @@ class DictConditionalWidget(BaseWidget): self.entity_widget.add_widget_to_layout(self, label) - def _prepare_entity_layouts(self, children, widget): - for child in children: + def _prepare_entity_layouts( + self, gui_layout, widget, parent_widget_by_entity_id + ): + for child in gui_layout: if not isinstance(child, dict): parent_widget_by_entity_id[child.id] = widget continue @@ -69,9 +71,11 @@ class DictConditionalWidget(BaseWidget): "Unknown Wrapper type \"{}\"".format(child["type"]) ) - self._parent_widget_by_entity_id[wrapper.id] = widget + parent_widget_by_entity_id[wrapper.id] = widget - self._prepare_entity_layouts(child["children"], wrapper) + self._prepare_entity_layouts( + child["children"], wrapper, parent_widget_by_entity_id + ) def _ui_item_base(self): self.setObjectName("DictInvisible") From 9131982be41add0330eaef9f0812e056eb194c13 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 19:59:37 +0200 Subject: [PATCH 036/110] added few required attributes --- openpype/tools/settings/settings/dict_conditional.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index aeb2b7d86c..47c1d7d4c9 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -18,6 +18,9 @@ class DictConditionalWidget(BaseWidget): def create_ui(self): self.input_fields = [] + self._content_by_enum_value = {} + self._last_enum_value = None + self.label_widget = None self.body_widget = None self.content_widget = None @@ -35,6 +38,7 @@ class DictConditionalWidget(BaseWidget): label = self.entity.label self._parent_widget_by_entity_id = {} + self._enum_key_by_wrapper_id = {} self._added_wrapper_ids = set() self._prepare_entity_layouts( self.entity.gui_layout, self.content_widget From 84f725b36447aca06ed676a2df3b44ede503dc8d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:02:02 +0200 Subject: [PATCH 037/110] modified how preparation of layout works --- .../settings/settings/dict_conditional.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 47c1d7d4c9..2287e52595 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -40,9 +40,22 @@ class DictConditionalWidget(BaseWidget): self._parent_widget_by_entity_id = {} self._enum_key_by_wrapper_id = {} self._added_wrapper_ids = set() - self._prepare_entity_layouts( - self.entity.gui_layout, self.content_widget - ) + + # Add enum entity to layout mapping + enum_entity = self.entity.enum_entity + self._parent_widget_by_entity_id[enum_entity.id] = self.content_widget + + # Add rest of entities to wrapper mappings + for enum_key, children in self.entity.gui_layout.items(): + parent_widget_by_entity_id = {} + self._prepare_entity_layouts( + children, + self.content_widget, + parent_widget_by_entity_id + ) + for item_id in parent_widget_by_entity_id.keys(): + self._enum_key_by_wrapper_id[item_id] = enum_key + self._parent_widget_by_entity_id.update(parent_widget_by_entity_id) for child_obj in self.entity.children: self.input_fields.append( From 2ebd5daac3be15bd3cc3feb68f863191f653df89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:02:56 +0200 Subject: [PATCH 038/110] store content of each enum key to different widget --- .../tools/settings/settings/dict_conditional.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 2287e52595..c2e59e0fbe 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -48,9 +48,22 @@ class DictConditionalWidget(BaseWidget): # Add rest of entities to wrapper mappings for enum_key, children in self.entity.gui_layout.items(): parent_widget_by_entity_id = {} + + content_widget = QtWidgets.QWidget(self.content_widget) + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setColumnStretch(0, 0) + content_layout.setColumnStretch(1, 1) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(5) + + self._content_by_enum_value[enum_key] = { + "widget": content_widget, + "layout": content_layout + } + self._prepare_entity_layouts( children, - self.content_widget, + content_widget, parent_widget_by_entity_id ) for item_id in parent_widget_by_entity_id.keys(): From cc72287ddbccb400edea8f25d6ac1850f2359609 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:03:26 +0200 Subject: [PATCH 039/110] modified how entity widgets are created and when --- .../settings/settings/dict_conditional.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index c2e59e0fbe..3798cffe38 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -70,12 +70,23 @@ class DictConditionalWidget(BaseWidget): self._enum_key_by_wrapper_id[item_id] = enum_key self._parent_widget_by_entity_id.update(parent_widget_by_entity_id) - for child_obj in self.entity.children: - self.input_fields.append( - self.create_ui_for_entity( - self.category_widget, child_obj, self + enum_input_field = self.create_ui_for_entity( + self.category_widget, self.entity.enum_entity, self + ) + self.enum_input_field = enum_input_field + self.input_fields.append(enum_input_field) + + for item_key, children in self.entity.children.items(): + content_widget = self._content_by_enum_value[item_key]["widget"] + row = self.content_layout.rowCount() + self.content_layout.addWidget(content_widget, row, 0, 1, 2) + + for child_obj in children: + self.input_fields.append( + self.create_ui_for_entity( + self.category_widget, child_obj, self + ) ) - ) if self.entity.use_label_wrap and self.content_layout.count() == 0: self.body_widget.hide_toolbox(True) From c8a5de88bd5918a302132e0e37fa1bc362a41b7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:03:52 +0200 Subject: [PATCH 040/110] define content widget based on map_id and entity id --- .../tools/settings/settings/dict_conditional.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 3798cffe38..013fefb74f 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -189,23 +189,30 @@ class DictConditionalWidget(BaseWidget): else: map_id = widget.entity.id + content_widget = self.content_widget + content_layout = self.content_layout + if map_id != self.entity.enum_entity.id: + enum_value = self._enum_key_by_wrapper_id[map_id] + content_widget = self._content_by_enum_value[enum_value]["widget"] + content_layout = self._content_by_enum_value[enum_value]["layout"] + wrapper = self._parent_widget_by_entity_id[map_id] - if wrapper is not self.content_widget: + if wrapper is not content_widget: wrapper.add_widget_to_layout(widget, label) if wrapper.id not in self._added_wrapper_ids: self.add_widget_to_layout(wrapper) self._added_wrapper_ids.add(wrapper.id) return - row = self.content_layout.rowCount() + row = content_layout.rowCount() if not label or isinstance(widget, WrapperWidget): - self.content_layout.addWidget(widget, row, 0, 1, 2) + content_layout.addWidget(widget, row, 0, 1, 2) else: label_widget = GridLabelWidget(label, widget) label_widget.input_field = widget widget.label_widget = label_widget - self.content_layout.addWidget(label_widget, row, 0, 1, 1) - self.content_layout.addWidget(widget, row, 1, 1, 1) + content_layout.addWidget(label_widget, row, 0, 1, 1) + content_layout.addWidget(widget, row, 1, 1, 1) def set_entity_value(self): for input_field in self.input_fields: From 313a78a3918cf279c0feceac64480b47afeb927e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:04:11 +0200 Subject: [PATCH 041/110] trigger change of visibility on change of enum --- openpype/tools/settings/settings/dict_conditional.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 013fefb74f..5442af14b4 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -273,7 +273,14 @@ class DictConditionalWidget(BaseWidget): self.label_widget.style().polish(self.label_widget) def _on_entity_change(self): - pass + enum_value = self.enum_input_field.entity.value + if enum_value == self._last_enum_value: + return + + self._last_enum_value = enum_value + for item_key, content in self._content_by_enum_value.items(): + widget = content["widget"] + widget.setVisible(item_key == enum_value) @property def is_invalid(self): From b344e0c27ad71350c887f2d90c3a2c5fe7ecb031 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:04:32 +0200 Subject: [PATCH 042/110] set_entity_value triggers on entity change --- openpype/tools/settings/settings/dict_conditional.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 5442af14b4..05dfa47e60 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -218,6 +218,8 @@ class DictConditionalWidget(BaseWidget): for input_field in self.input_fields: input_field.set_entity_value() + self._on_entity_change() + def hierarchical_style_update(self): self.update_style() for input_field in self.input_fields: From c9ee4e5f713f20dec0d4f684a596147b71f58850 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:04:45 +0200 Subject: [PATCH 043/110] added column stretch to grid layout --- openpype/tools/settings/settings/dict_conditional.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 05dfa47e60..84288f7b5b 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -41,6 +41,9 @@ class DictConditionalWidget(BaseWidget): self._enum_key_by_wrapper_id = {} self._added_wrapper_ids = set() + self.content_layout.setColumnStretch(0, 0) + self.content_layout.setColumnStretch(1, 1) + # Add enum entity to layout mapping enum_entity = self.entity.enum_entity self._parent_widget_by_entity_id[enum_entity.id] = self.content_widget From e1129bdbad5a167e4428bd3d298e1e4e5012fdd9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:25:45 +0200 Subject: [PATCH 044/110] fix keys method --- openpype/settings/entities/dict_conditional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8fc22348a7..f72d1c8b82 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -105,7 +105,7 @@ class DictConditionalEntity(ItemEntity): def keys(self): """Entity's keys.""" keys = list(self.non_gui_children[self.current_enum].keys()) - keys.insert(0, [self.current_enum]) + keys.insert(0, [self.enum_key]) return keys def values(self): From 4bff4a8138506f1fdbe247e54d4fb739bb111bff Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:29:07 +0200 Subject: [PATCH 045/110] force to be a group --- openpype/settings/entities/dict_conditional.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index f72d1c8b82..2e8cd6affe 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -149,6 +149,13 @@ class DictConditionalEntity(ItemEntity): self._current_metadata = {} self._metadata_are_modified = False + if ( + self.group_item is None + and not self.is_dynamic_item + and not self.is_in_dynamic_item + ): + self.is_group = True + # Children are stored by key as keys are immutable and are defined by # schema self.valid_value_types = (dict, ) From 40f87f2f20c5fee45f2be2e903aaf9814f9acf43 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:30:11 +0200 Subject: [PATCH 046/110] few minor fixes of entity --- .../settings/entities/dict_conditional.py | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 2e8cd6affe..d3aad60df6 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -391,7 +391,7 @@ class DictConditionalEntity(ItemEntity): @property def value(self): output = { - self.current_enum: self.enum_entity.value + self.enum_key: self.enum_entity.value } for key, child_obj in self.non_gui_children[self.current_enum].items(): output[key] = child_obj.value @@ -408,6 +408,7 @@ class DictConditionalEntity(ItemEntity): def _child_has_unsaved_changes(self): if self.enum_entity.has_unsaved_changes: return True + for child_obj in self.non_gui_children[self.current_enum].values(): if child_obj.has_unsaved_changes: return True @@ -448,11 +449,14 @@ class DictConditionalEntity(ItemEntity): return NOT_SET if self._override_state is OverrideState.DEFAULTS: - output = { - self.current_enum: self.enum_entity.settings_value() - } - non_gui_children = self.non_gui_children[self.current_enum] - for key, child_obj in non_gui_children.items(): + children_items = [ + (self.enum_key, self.enum_entity) + ] + for item in self.non_gui_children[self.current_enum].items(): + children_items.append(item) + + output = {} + for key, child_obj in children_items: child_value = child_obj.settings_value() if not child_obj.is_file and not child_obj.file_item: for _key, _value in child_value.items(): @@ -470,10 +474,14 @@ class DictConditionalEntity(ItemEntity): if not self.has_project_override: return NOT_SET - output = { - self.current_enum: self.enum_entity.settings_value() - } - for key, child_obj in self.non_gui_children[self.current_enum].items(): + output = {} + children_items = [ + (self.enum_key, self.enum_entity) + ] + for item in self.non_gui_children[self.current_enum].items(): + children_items.append(item) + + for key, child_obj in children_items: value = child_obj.settings_value() if value is not NOT_SET: output[key] = value @@ -537,7 +545,7 @@ class DictConditionalEntity(ItemEntity): value_keys = set(value.keys()) enum_value = value[self.enum_key] - expected_keys = set(self.non_gui_children[enum_value]) + expected_keys = set(self.non_gui_children[enum_value].keys()) expected_keys.add(self.enum_key) unknown_keys = value_keys - expected_keys if unknown_keys: @@ -549,7 +557,7 @@ class DictConditionalEntity(ItemEntity): ) self.enum_entity.update_default_value(enum_value) - for children_by_key in self.non_gui_children.items(): + for children_by_key in self.non_gui_children.values(): for key, child_obj in children_by_key.items(): child_value = value.get(key, NOT_SET) child_obj.update_default_value(child_value) @@ -565,10 +573,10 @@ class DictConditionalEntity(ItemEntity): self.had_studio_override = metadata is not NOT_SET if value is NOT_SET: - self.enum_entity.update_default_value(value) + self.enum_entity.update_studio_value(value) for children_by_key in self.non_gui_children.values(): for child_obj in children_by_key.values(): - child_obj.update_default_value(value) + child_obj.update_studio_value(value) return value_keys = set(value.keys()) @@ -601,10 +609,10 @@ class DictConditionalEntity(ItemEntity): self.had_project_override = metadata is not NOT_SET if value is NOT_SET: - self.enum_entity.update_default_value(value) + self.enum_entity.update_project_value(value) for children_by_key in self.non_gui_children.values(): for child_obj in children_by_key.values(): - child_obj.update_default_value(value) + child_obj.update_project_value(value) return value_keys = set(value.keys()) From 3d59ba17d54600247593a7c05c00a0576e124dc1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:37:04 +0200 Subject: [PATCH 047/110] current_enum is dynamic property --- openpype/settings/entities/dict_conditional.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index d3aad60df6..6802af5806 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -171,10 +171,15 @@ class DictConditionalEntity(ItemEntity): self.enum_children = self.schema_data.get("enum_children") self.enum_entity = None - self.current_enum = None self._add_children() + @property + def current_enum(self): + if self.enum_entity is None: + return None + return self.enum_entity.value + def schema_validations(self): """Validation of schema data.""" if self.enum_key is None: @@ -269,25 +274,20 @@ class DictConditionalEntity(ItemEntity): if not self.enum_children or not self.enum_key: return - enum_items = [] valid_enum_items = [] for item in self.enum_children: if isinstance(item, dict) and "key" in item: valid_enum_items.append(item) - first_key = None + enum_items = [] for item in valid_enum_items: item_key = item["key"] - if first_key is None: - first_key = item_key item_label = item.get("label") or item_key enum_items.append({item_key: item_label}) if not enum_items: return - self.current_enum = first_key - enum_key = self.enum_key or "invalid" enum_schema = { "type": "enum", @@ -296,6 +296,7 @@ class DictConditionalEntity(ItemEntity): "key": enum_key, "label": self.enum_label or enum_key } + enum_entity = self.create_schema_object(enum_schema, self) self.enum_entity = enum_entity From f3ae791f5c0afc283a5a17490bb70bf362e4a1e4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:56:13 +0200 Subject: [PATCH 048/110] make sure all keys are available in all variables --- openpype/settings/entities/dict_conditional.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 6802af5806..956180c3da 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -159,9 +159,9 @@ class DictConditionalEntity(ItemEntity): # Children are stored by key as keys are immutable and are defined by # schema self.valid_value_types = (dict, ) - self.children = collections.defaultdict(list) - self.non_gui_children = collections.defaultdict(dict) - self.gui_layout = collections.defaultdict(list) + self.children = {} + self.non_gui_children = {} + self.gui_layout = {} if self.is_dynamic_item: self.require_key = False @@ -302,6 +302,11 @@ class DictConditionalEntity(ItemEntity): for item in valid_enum_items: item_key = item["key"] + # Make sure all keys have set value in there variables + self.non_gui_children[item_key] = {} + self.children[item_key] = [] + self.gui_layout[item_key] = [] + children = item.get("children") or [] for children_schema in children: child_obj = self.create_schema_object(children_schema, self) From 0e0e527741392bad6a67de9f3d2736521a149326 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 12:44:50 +0200 Subject: [PATCH 049/110] items vs. values fix --- openpype/settings/entities/dict_conditional.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 956180c3da..858c2ca4e8 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -599,7 +599,7 @@ class DictConditionalEntity(ItemEntity): ) self.enum_entity.update_studio_value(enum_value) - for children_by_key in self.non_gui_children.items(): + for children_by_key in self.non_gui_children.values(): for key, child_obj in children_by_key.items(): child_value = value.get(key, NOT_SET) child_obj.update_studio_value(child_value) @@ -635,7 +635,7 @@ class DictConditionalEntity(ItemEntity): ) self.enum_entity.update_project_value(enum_value) - for children_by_key in self.non_gui_children.items(): + for children_by_key in self.non_gui_children.values(): for key, child_obj in children_by_key.items(): child_value = value.get(key, NOT_SET) child_obj.update_project_value(child_value) From d58c8f1a112a0daaf2e3227794923d0262ee344f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 12:46:10 +0200 Subject: [PATCH 050/110] added argument ignore_missing_defaults to set_override_state method --- openpype/settings/entities/base_entity.py | 9 ++++++++- openpype/settings/entities/dict_conditional.py | 2 +- .../entities/dict_immutable_keys_entity.py | 4 ++-- .../entities/dict_mutable_keys_entity.py | 14 ++++++++++---- openpype/settings/entities/input_entities.py | 12 +++++++++--- openpype/settings/entities/item_entities.py | 18 ++++++++++++------ openpype/settings/entities/list_entity.py | 16 ++++++++++++---- openpype/settings/entities/root_entities.py | 7 +++++-- 8 files changed, 59 insertions(+), 23 deletions(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index 0e29a35e1f..e1cd5134e7 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -280,7 +280,7 @@ class BaseItemEntity(BaseEntity): ) @abstractmethod - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): """Set override state and trigger it on children. Method discard all changes in hierarchy and use values, metadata @@ -290,8 +290,15 @@ class BaseItemEntity(BaseEntity): Should start on root entity and when triggered then must be called on all entities in hierarchy. + Argument `ignore_missing_defaults` should be used when entity has + children that are not saved or used all the time but override statu + must be changed and children must have any default value. + Args: state (OverrideState): State to which should be data changed. + ignore_missing_defaults (bool): Ignore missing default values. + Entity won't raise `DefaultsNotDefined` and + `StudioDefaultsNotDefined`. """ pass diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 858c2ca4e8..98aa10dacb 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -377,7 +377,7 @@ class DictConditionalEntity(ItemEntity): self._metadata_are_modified = current_metadata != metadata self._current_metadata = current_metadata - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index c965dc3b5a..2802290e68 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -258,7 +258,7 @@ class DictImmutableKeysEntity(ItemEntity): self._metadata_are_modified = current_metadata != metadata self._current_metadata = current_metadata - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -268,7 +268,7 @@ class DictImmutableKeysEntity(ItemEntity): self._override_state = state for child_obj in self.non_gui_children.values(): - child_obj.set_override_state(state) + child_obj.set_override_state(state, ignore_missing_defaults) self._update_current_metadata() diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index 3c2645e3e5..a5734e36b8 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -320,7 +320,7 @@ class DictMutableKeysEntity(EndpointEntity): def _metadata_for_current_state(self): return self._get_metadata_for_state(self._override_state) - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -331,11 +331,17 @@ class DictMutableKeysEntity(EndpointEntity): # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: - if not self.has_default_value: + if ( + not self.has_default_value + and not ignore_missing_defaults + ): raise DefaultsNotDefined(self) elif state > OverrideState.STUDIO: - if not self.had_studio_override: + if ( + not self.had_studio_override + and not ignore_missing_defaults + ): raise StudioDefaultsNotDefined(self) if state is OverrideState.STUDIO: @@ -426,7 +432,7 @@ class DictMutableKeysEntity(EndpointEntity): if label: children_label_by_id[child_entity.id] = label - child_entity.set_override_state(state) + child_entity.set_override_state(state, ignore_missing_defaults) self.children_label_by_id = children_label_by_id diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 295333eb60..9b41a26bdb 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -217,7 +217,7 @@ class InputEntity(EndpointEntity): return True return False - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -227,11 +227,17 @@ class InputEntity(EndpointEntity): # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: - if not self.has_default_value: + if ( + not self.has_default_value + and not ignore_missing_defaults + ): raise DefaultsNotDefined(self) elif state > OverrideState.STUDIO: - if not self.had_studio_override: + if ( + not self.had_studio_override + and not ignore_missing_defaults + ): raise StudioDefaultsNotDefined(self) if state is OverrideState.STUDIO: diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index 48336080b6..c52eab988f 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -150,14 +150,14 @@ class PathEntity(ItemEntity): def value(self): return self.child_obj.value - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) return self._override_state = state - self.child_obj.set_override_state(state) + self.child_obj.set_override_state(state, ignore_missing_defaults) def update_default_value(self, value): self.child_obj.update_default_value(value) @@ -344,7 +344,7 @@ class ListStrictEntity(ItemEntity): return True return False - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -354,15 +354,21 @@ class ListStrictEntity(ItemEntity): # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: - if not self.has_default_value: + if ( + not self.has_default_value + and not ignore_missing_defaults + ): raise DefaultsNotDefined(self) elif state > OverrideState.STUDIO: - if not self.had_studio_override: + if ( + not self.had_studio_override + and not ignore_missing_defaults + ): raise StudioDefaultsNotDefined(self) for child_entity in self.children: - child_entity.set_override_state(state) + child_entity.set_override_state(state, ignore_missing_defaults) self.initial_value = self.settings_value() diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 4b3f7a2659..2225523792 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -205,7 +205,7 @@ class ListEntity(EndpointEntity): self._has_project_override = True self.on_change() - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -219,11 +219,17 @@ class ListEntity(EndpointEntity): # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: - if not self.has_default_value: + if ( + not self.has_default_value + and not ignore_missing_defaults + ): raise DefaultsNotDefined(self) elif state > OverrideState.STUDIO: - if not self.had_studio_override: + if ( + not self.had_studio_override + and not ignore_missing_defaults + ): raise StudioDefaultsNotDefined(self) value = NOT_SET @@ -257,7 +263,9 @@ class ListEntity(EndpointEntity): child_obj.update_studio_value(item) for child_obj in self.children: - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, ignore_missing_defaults + ) self.initial_value = self.settings_value() diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index 1833535a07..b758e30cbe 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -218,7 +218,7 @@ class RootEntity(BaseItemEntity): schema_data, *args, **kwargs ) - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults=None): """Set override state and trigger it on children. Method will discard all changes in hierarchy and use values, metadata @@ -227,9 +227,12 @@ class RootEntity(BaseItemEntity): Args: state (OverrideState): State to which should be data changed. """ + if not ignore_missing_defaults: + ignore_missing_defaults = False + self._override_state = state for child_obj in self.non_gui_children.values(): - child_obj.set_override_state(state) + child_obj.set_override_state(state, ignore_missing_defaults) def on_change(self): """Trigger callbacks on change.""" From 0ed3a2ee701f26fc9cdebe19ceefb225848ae38c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 12:47:12 +0200 Subject: [PATCH 051/110] Use ignore missing defaults in conditional dictionary children that are not using current enum value --- openpype/settings/entities/dict_conditional.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 98aa10dacb..112ef8bddc 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -386,11 +386,17 @@ class DictConditionalEntity(ItemEntity): # Change has/had override states self._override_state = state - self.enum_entity.set_override_state(state) + self.enum_entity.set_override_state(state, ignore_missing_defaults) + + for child_obj in self.non_gui_children[self.current_enum].values(): + child_obj.set_override_state(state, ignore_missing_defaults) + + for item_key, children_by_key in self.non_gui_children.items(): + if item_key == self.current_enum: + continue - for children_by_key in self.non_gui_children.values(): for child_obj in children_by_key.values(): - child_obj.set_override_state(state) + child_obj.set_override_state(state, True) self._update_current_metadata() From c1d6db4356e7258970dbffb9152a82f408bab025 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 13:04:52 +0200 Subject: [PATCH 052/110] added few comments and docstring --- .../settings/entities/dict_conditional.py | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 112ef8bddc..6e28cbd591 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -73,6 +73,19 @@ example_schema = { class DictConditionalEntity(ItemEntity): + """Entity represents dictionay with only one persistent key definition. + + The persistent key is enumerator which define rest of children under + dictionary. There is not possibility of shared children. + + Entity's keys can't be removed or added. But they may change based on + the persistent key. If you're change value manually (key by key) make sure + you'll change value of the persistent key as first. It is recommended to + use `set` method which handle this for you. + + It is possible to use entity similar way as `dict` object. Returned values + are not real settings values but entities representing the value. + """ schema_types = ["dict-conditional"] _default_label_wrap = { "use_label_wrap": False, @@ -149,6 +162,7 @@ class DictConditionalEntity(ItemEntity): self._current_metadata = {} self._metadata_are_modified = False + # Entity must be group or in group if ( self.group_item is None and not self.is_dynamic_item @@ -176,15 +190,21 @@ class DictConditionalEntity(ItemEntity): @property def current_enum(self): + """Current value of enum entity. + + This value define what children are used. + """ if self.enum_entity is None: return None return self.enum_entity.value def schema_validations(self): """Validation of schema data.""" + # Enum key must be defined if self.enum_key is None: raise EntitySchemaError(self, "Key 'enum_key' is not set.") + # Validate type of enum children if not isinstance(self.enum_children, list): raise EntitySchemaError( self, "Key 'enum_children' must be a list. Got: {}".format( @@ -192,6 +212,7 @@ class DictConditionalEntity(ItemEntity): ) ) + # Without defined enum children entity has nothing to do if not self.enum_children: raise EntitySchemaError(self, ( "Key 'enum_children' have empty value. Entity can't work" @@ -219,6 +240,7 @@ class DictConditionalEntity(ItemEntity): raise SchemaDuplicatedKeys(self, key) children_def_keys.append(key) + # Validate key duplications per each enum item for children in self.children.values(): children_keys = set() children_keys.add(self.enum_key) @@ -230,6 +252,7 @@ class DictConditionalEntity(ItemEntity): else: raise SchemaDuplicatedKeys(self, child_entity.key) + # Validate all remaining keys with key regex for children_by_key in self.non_gui_children.values(): for key in children_by_key.keys(): if not KEY_REGEX.match(key): @@ -270,7 +293,8 @@ class DictConditionalEntity(ItemEntity): All children are stored by their enum item. """ - # Skip and wait for validation + # Skip if are not defined + # - schema validations should raise and exception if not self.enum_children or not self.enum_key: return @@ -288,6 +312,7 @@ class DictConditionalEntity(ItemEntity): if not enum_items: return + # Create Enum child first enum_key = self.enum_key or "invalid" enum_schema = { "type": "enum", @@ -300,9 +325,11 @@ class DictConditionalEntity(ItemEntity): enum_entity = self.create_schema_object(enum_schema, self) self.enum_entity = enum_entity + # Create children per each enum item for item in valid_enum_items: item_key = item["key"] - # Make sure all keys have set value in there variables + # Make sure all keys have set value in these variables + # - key 'children' is optional self.non_gui_children[item_key] = {} self.children[item_key] = [] self.gui_layout[item_key] = [] @@ -386,11 +413,15 @@ class DictConditionalEntity(ItemEntity): # Change has/had override states self._override_state = state + # Set override state on enum entity first self.enum_entity.set_override_state(state, ignore_missing_defaults) + # Set override state on other entities under current enum value for child_obj in self.non_gui_children[self.current_enum].values(): child_obj.set_override_state(state, ignore_missing_defaults) + # Set override state on other enum children + # - these must not raise exception about missing defaults for item_key, children_by_key in self.non_gui_children.items(): if item_key == self.current_enum: continue From 3b217c57a2c8b2069b4cf3e4f5a03acbe66e90ff Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 13:05:02 +0200 Subject: [PATCH 053/110] added enum key validation --- openpype/settings/entities/dict_conditional.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 6e28cbd591..96e6c518f3 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -252,6 +252,10 @@ class DictConditionalEntity(ItemEntity): else: raise SchemaDuplicatedKeys(self, child_entity.key) + # Enum key must match key regex + if not KEY_REGEX.match(self.enum_key): + raise InvalidKeySymbols(self.path, self.enum_key) + # Validate all remaining keys with key regex for children_by_key in self.non_gui_children.values(): for key in children_by_key.keys(): From 605a0454b2c69ce3e76a4df85eced6e0364ae47a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 13:05:20 +0200 Subject: [PATCH 054/110] include enum_key in builtin methods --- openpype/settings/entities/dict_conditional.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 96e6c518f3..9ba24cf0de 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -95,11 +95,16 @@ class DictConditionalEntity(ItemEntity): def __getitem__(self, key): """Return entity inder key.""" + if key == self.enum_key: + return self.enum_entity return self.non_gui_children[self.current_enum][key] def __setitem__(self, key, value): """Set value of item under key.""" - child_obj = self.non_gui_children[self.current_enum][key] + if key == self.enum_key: + child_obj = self.enum_entity + else: + child_obj = self.non_gui_children[self.current_enum][key] child_obj.set(value) def __iter__(self): @@ -109,10 +114,14 @@ class DictConditionalEntity(ItemEntity): def __contains__(self, key): """Check if key is available.""" + if key == self.enum_key: + return True return key in self.non_gui_children[self.current_enum] def get(self, key, default=None): """Safe entity getter by key.""" + if key == self.enum_key: + return self.enum_entity return self.non_gui_children[self.current_enum].get(key, default) def keys(self): From 082e453d138055a86c25e8e2ad09060f28ff5d11 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 14:24:08 +0200 Subject: [PATCH 055/110] fix variable name usage --- openpype/tools/settings/settings/item_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index b23372e9ac..82afbb0a13 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -145,7 +145,7 @@ class DictImmutableKeysWidget(BaseWidget): self.content_widget = content_widget self.content_layout = content_layout - if len(self.input_fields) == 1 and self.checkbox_widget: + if len(self.input_fields) == 1 and self.checkbox_child: body_widget.hide_toolbox(hide_content=True) elif self.entity.collapsible: From 905db947bbf3ae5d53aade3c16f1b2de08c63def Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 14:40:49 +0200 Subject: [PATCH 056/110] added dict-conditional to readme --- openpype/settings/entities/schemas/README.md | 97 ++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index bbd53fa46b..3c360b892f 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -181,6 +181,103 @@ } ``` +## dict-conditional +- is similar to `dict` but has only one child entity that will be always available +- the one entity is enumerator of possible values and based on value of the entity are defined and used other children entities +- each value of enumerator have defined children that will be used + - there is no way how to have shared entities across multiple enum items +- value from enumerator is also stored next to other values + - to define the key under which will be enum value stored use `enum_key` + - `enum_key` must match key regex and any enum item can't have children with same key + - `enum_label` is label of the entity for UI purposes +- enum items are define with `enum_children` + - it's a list where each item represents enum item + - all items in `enum_children` must have at least `key` key which represents value stored under `enum_key` + - items can define `label` for UI purposes + - most important part is that item can define `children` key where are definitions of it's children (`children` value works the same way as in `dict`) +- entity must have defined `"label"` if is not used as widget +- is set as group if any parent is not group +- if `"label"` is entetered there which will be shown in GUI + - item with label can be collapsible + - that can be set with key `"collapsible"` as `True`/`False` (Default: `True`) + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + - it is possible to add darker background with `"highlight_content"` (Default: `False`) + - darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color + - output is dictionary `{the "key": children values}` +``` +# Example +{ + "type": "dict-conditional", + "key": "my_key", + "label": "My Key", + "enum_key": "type", + "enum_label": "label", + "enum_children": [ + # Each item must be a dictionary with 'key' + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "command", + "label": "Comand" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": "text" + } + ] + }, + { + # Separator does not have children as "separator" value is enough + "key": "separator", + "label": "Separator" + } + ] +} +``` + +How output of the schema could look like on save: +``` +{ + "type": "separator" +} + +{ + "type": "action", + "key": "action_1", + "label": "Action 1", + "command": "run command -arg" +} + +{ + "type": "menu", + "children": [ + "child_1", + "child_2" + ] +} +``` + ## Inputs for setting any kind of value (`Pure` inputs) - all these input must have defined `"key"` under which will be stored and `"label"` which will be shown next to input - unless they are used in different types of inputs (later) "as widgets" in that case `"key"` and `"label"` are not required as there is not place where to set them From df37c2e1fffbeea1baf66c5061ac2699ed9d5a7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 14:50:07 +0200 Subject: [PATCH 057/110] removed example schema --- .../settings/entities/dict_conditional.py | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 9ba24cf0de..0e6540e606 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -24,54 +24,6 @@ from .exceptions import ( ) -example_schema = { - "type": "dict-conditional", - "key": "KEY", - "label": "LABEL", - "enum_key": "type", - "enum_label": "label", - "enum_children": [ - { - "key": "action", - "label": "Action", - "children": [ - { - "type": "text", - "key": "key", - "label": "Key" - }, - { - "type": "text", - "key": "label", - "label": "Label" - }, - { - "type": "text", - "key": "command", - "label": "Comand" - } - ] - }, - { - "key": "menu", - "label": "Menu", - "children": [ - { - "key": "children", - "label": "Children", - "type": "list", - "object_type": "text" - } - ] - }, - { - "key": "separator", - "label": "Separator" - } - ] -} - - class DictConditionalEntity(ItemEntity): """Entity represents dictionay with only one persistent key definition. From 11e8e3a8cb269d7d6de06aca6d493f7f8258fc16 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 29 Jun 2021 16:43:20 +0200 Subject: [PATCH 058/110] drop support for <4.26 --- openpype/hosts/unreal/README.md | 9 ++ openpype/hosts/unreal/api/lib.py | 122 +++++------------- .../unreal/hooks/pre_workfile_preparation.py | 23 ++-- .../system_settings/applications.json | 2 +- .../schema_project_unreal.json | 5 - 5 files changed, 55 insertions(+), 106 deletions(-) create mode 100644 openpype/hosts/unreal/README.md diff --git a/openpype/hosts/unreal/README.md b/openpype/hosts/unreal/README.md new file mode 100644 index 0000000000..0a69b9e0cf --- /dev/null +++ b/openpype/hosts/unreal/README.md @@ -0,0 +1,9 @@ +## Unreal Integration + +Supported Unreal Engine version is 4.26+ (mainly because of major Python changes done there). + +### Project naming +Unreal doesn't support project names starting with non-alphabetic character. So names like `123_myProject` are +invalid. If OpenPype detects such name it automatically prepends letter **P** to make it valid name, so `123_myProject` +will become `P123_myProject`. There is also soft-limit on project name length to be shorter than 20 characters. +Longer names will issue warning in Unreal Editor that there might be possible side effects. \ No newline at end of file diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 6231fd6f33..144f781171 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -70,6 +70,21 @@ def get_engine_versions(env=None): return OrderedDict() +def get_editor_executable_path(engine_path: Path) -> Path: + """Get UE4 Editor executable path.""" + ue4_path = engine_path / "Engine/Binaries" + if platform.system().lower() == "windows": + ue4_path /= "Win64/UE4Editor.exe" + + elif platform.system().lower() == "linux": + ue4_path /= "Linux/UE4Editor" + + elif platform.system().lower() == "darwin": + ue4_path /= "Mac/UE4Editor" + + return ue4_path + + def _win_get_engine_versions(): """Get Unreal Engine versions on Windows. @@ -244,39 +259,6 @@ def create_unreal_project(project_name: str, ] } - if preset["install_unreal_python_engine"]: - # WARNING: This is deprecated as Unreal Engine Python project - # is on hold and is mainly replaced in 4.26 by Epics own - # Python implementation. - # --------------------------------------------------------------- - # If `OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from - # there to support offline installation. - # Otherwise clone UnrealEnginePython to Plugins directory - # https://github.com/20tab/UnrealEnginePython.git - uep_path = plugins_path / "UnrealEnginePython" - if env.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"): - - os.makedirs(uep_path, exist_ok=True) - dir_util._path_created = {} - dir_util.copy_tree( - env.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"), - uep_path.as_posix()) - else: - # WARNING: this will trigger dev_mode, because we need to compile - # this plugin. - dev_mode = True - import git - git.Repo.clone_from( - "https://github.com/20tab/UnrealEnginePython.git", - uep_path.as_posix()) - - data["Plugins"].append( - {"Name": "UnrealEnginePython", "Enabled": True}) - - if not (uep_path / "Binaries").is_dir() \ - or not (uep_path / "Intermediate").is_dir(): - dev_mode = True - if dev_mode or preset["dev_mode"]: # this will add project module and necessary source file to make it # C++ project and to (hopefully) make Unreal Editor to compile all @@ -289,73 +271,31 @@ def create_unreal_project(project_name: str, "AdditionalDependencies": ["Engine"], }] - if preset["install_unreal_python_engine"]: - # now we need to fix python path in: - # `UnrealEnginePython.Build.cs` - # to point to our python - with open(uep_path / "Source" / "UnrealEnginePython" / - "UnrealEnginePython.Build.cs", mode="r") as f: - build_file = f.read() - - fix = build_file.replace( - 'private string pythonHome = "";', - 'private string pythonHome = "{}";'.format( - sys.base_prefix.replace("\\", "/"))) - - with open(uep_path / "Source" / "UnrealEnginePython" / - "UnrealEnginePython.Build.cs", mode="w") as f: - f.write(fix) - # write project file project_file = pr_dir / f"{project_name}.uproject" with open(project_file, mode="w") as pf: json.dump(data, pf, indent=4) - # ensure we have PySide installed in engine - # this won't work probably as pyside is no longer on pypi - # DEPRECATED: support for python 2 in UE4 is dropped. + # ensure we have PySide2 installed in engine python_path = None - if int(ue_version.split(".")[0]) == 4 and \ - int(ue_version.split(".")[1]) < 25: - if platform.system().lower() == "windows": - python_path = engine_path / ("Engine/Binaries/ThirdParty/" - "Python/Win64/python.exe") + if platform.system().lower() == "windows": + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python3/Win64/pythonw.exe") - if platform.system().lower() == "linux": - python_path = engine_path / ("Engine/Binaries/ThirdParty/" - "Python/Linux/bin/python") + if platform.system().lower() == "linux": + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python3/Linux/bin/python3") - if platform.system().lower() == "darwin": - python_path = engine_path / ("Engine/Binaries/ThirdParty/" - "Python/Mac/bin/python") + if platform.system().lower() == "darwin": + python_path = engine_path / ("Engine/Binaries/ThirdParty/" + "Python3/Mac/bin/python3") - if python_path.exists(): - subprocess.run([python_path.as_posix(), "-m", - "pip", "install", "pyside"]) - else: - raise NotImplementedError("Unsupported platform") - else: - # install PySide2 inside newer engines - if platform.system().lower() == "windows": - python_path = engine_path / ("Engine/Binaries/ThirdParty/" - "Python3/Win64/pythonw.exe") - - if platform.system().lower() == "linux": - python_path = engine_path / ("Engine/Binaries/ThirdParty/" - "Python3/Linux/bin/python3") - - if platform.system().lower() == "darwin": - python_path = engine_path / ("Engine/Binaries/ThirdParty/" - "Python3/Mac/bin/python3") - - if python_path: - if not python_path.exists(): - raise RuntimeError( - f"Unreal Python not found at {python_path}") - subprocess.run([python_path.as_posix(), "-m", - "pip", "install", "pyside2"]) - else: - raise NotImplementedError("Unsupported platform") + if not python_path: + raise NotImplementedError("Unsupported platform") + if not python_path.exists(): + raise RuntimeError(f"Unreal Python not found at {python_path}") + subprocess.run( + [python_path.as_posix(), "-m", "pip", "install", "pyside2"]) if dev_mode or preset["dev_mode"]: _prepare_cpp_project(project_file, engine_path) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 2c4dd822bc..fcdec7a96c 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -33,6 +33,18 @@ class UnrealPrelaunchHook(PreLaunchHook): workdir = self.launch_context.env["AVALON_WORKDIR"] engine_version = self.app_name.split("/")[-1].replace("-", ".") unreal_project_name = f"{asset_name}_{task_name}" + try: + if int(engine_version.split(".")[0]) < 4 and \ + int(engine_version.split(".")[1]) < 26: + raise ApplicationLaunchFailed(( + f"{self.signature} Old unsupported version of UE4 " + f"detected - {engine_version}")) + except ValueError: + # there can be string in minor version and in that case + # int cast is failing. This probably happens only with + # early access versions and is of no concert for this check + # so lets keep it quite. + ... # Unreal is sensitive about project names longer then 20 chars if len(unreal_project_name) > 20: @@ -75,15 +87,8 @@ class UnrealPrelaunchHook(PreLaunchHook): f"detected [ {engine_version} ]" )) - ue4_path = Path(detected[engine_version]) / "Engine/Binaries" - if platform.system().lower() == "windows": - ue4_path = ue4_path / "Win64/UE4Editor.exe" - - elif platform.system().lower() == "linux": - ue4_path = ue4_path / "Linux/UE4Editor" - - elif platform.system().lower() == "darwin": - ue4_path = ue4_path / "Mac/UE4Editor" + ue4_path = unreal_lib.get_editor_executable_path( + Path(detected[engine_version])) self.launch_context.launch_args.append(ue4_path.as_posix()) project_path.mkdir(parents=True, exist_ok=True) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 72cd010cf2..fae89a36ca 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -1095,7 +1095,7 @@ "unreal": { "enabled": true, "label": "Unreal Editor", - "icon": "{}/app_icons/ue4.png'", + "icon": "{}/app_icons/ue4.png", "host_name": "unreal", "environment": {}, "variants": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index b6e94d9d03..4e197e9fc8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -15,11 +15,6 @@ "type": "boolean", "key": "dev_mode", "label": "Dev mode" - }, - { - "type": "boolean", - "key": "install_unreal_python_engine", - "label": "Install unreal python engine" } ] } From f6700aadf3ec3b217d7775f3f7dcde2edb8f4820 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 29 Jun 2021 16:52:20 +0200 Subject: [PATCH 059/110] fix hound --- openpype/hosts/unreal/api/lib.py | 2 -- openpype/hosts/unreal/hooks/pre_workfile_preparation.py | 1 - 2 files changed, 3 deletions(-) diff --git a/openpype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py index 144f781171..7e34c3ff15 100644 --- a/openpype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -230,8 +230,6 @@ def create_unreal_project(project_name: str, ue_id = "{" + loaded_modules.get("BuildId") + "}" plugins_path = None - uep_path = None - if os.path.isdir(env.get("AVALON_UNREAL_PLUGIN", "")): # copy plugin to correct path under project plugins_path = pr_dir / "Plugins" diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index fcdec7a96c..01b8b6bc05 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -2,7 +2,6 @@ """Hook to launch Unreal and prepare projects.""" import os from pathlib import Path -import platform from openpype.lib import ( PreLaunchHook, From 672ee1d98db2a7adf341a63e635834ede87d139d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 16:57:01 +0200 Subject: [PATCH 060/110] store ignore_missing_defaults and reuse it on callbacks --- openpype/settings/entities/base_entity.py | 1 + openpype/settings/entities/dict_conditional.py | 1 + .../entities/dict_immutable_keys_entity.py | 1 + .../entities/dict_mutable_keys_entity.py | 18 ++++++++++++++---- openpype/settings/entities/input_entities.py | 1 + openpype/settings/entities/item_entities.py | 2 ++ openpype/settings/entities/list_entity.py | 18 ++++++++++++++---- 7 files changed, 34 insertions(+), 8 deletions(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index e1cd5134e7..6c2f382403 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -136,6 +136,7 @@ class BaseItemEntity(BaseEntity): # Override state defines which values are used, saved and how. # TODO convert to private attribute self._override_state = OverrideState.NOT_DEFINED + self._ignore_missing_defaults = None # These attributes may change values during existence of an object # Default value, studio override values and project override values diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 0e6540e606..33cedd7b54 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -377,6 +377,7 @@ class DictConditionalEntity(ItemEntity): # Change has/had override states self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults # Set override state on enum entity first self.enum_entity.set_override_state(state, ignore_missing_defaults) diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index 2802290e68..bde5304787 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -266,6 +266,7 @@ class DictImmutableKeysEntity(ItemEntity): # Change has/had override states self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults for child_obj in self.non_gui_children.values(): child_obj.set_override_state(state, ignore_missing_defaults) diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index a5734e36b8..c3df935269 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -154,7 +154,9 @@ class DictMutableKeysEntity(EndpointEntity): def add_key(self, key): new_child = self._add_key(key) - new_child.set_override_state(self._override_state) + new_child.set_override_state( + self._override_state, self._ignore_missing_defaults + ) self.on_change() return new_child @@ -328,6 +330,8 @@ class DictMutableKeysEntity(EndpointEntity): # TODO change metadata self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults + # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: @@ -616,7 +620,9 @@ class DictMutableKeysEntity(EndpointEntity): if not self._can_discard_changes: return - self.set_override_state(self._override_state) + self.set_override_state( + self._override_state, self._ignore_missing_defaults + ) on_change_trigger.append(self.on_change) def _add_to_studio_default(self, _on_change_trigger): @@ -651,7 +657,9 @@ class DictMutableKeysEntity(EndpointEntity): if label: children_label_by_id[child_entity.id] = label - child_entity.set_override_state(self._override_state) + child_entity.set_override_state( + self._override_state, self._ignore_missing_defaults + ) self.children_label_by_id = children_label_by_id @@ -700,7 +708,9 @@ class DictMutableKeysEntity(EndpointEntity): if label: children_label_by_id[child_entity.id] = label - child_entity.set_override_state(self._override_state) + child_entity.set_override_state( + self._override_state, self._ignore_missing_defaults + ) self.children_label_by_id = children_label_by_id diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 9b41a26bdb..2abb7a2253 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -224,6 +224,7 @@ class InputEntity(EndpointEntity): return self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index c52eab988f..7e84f8c801 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -157,6 +157,7 @@ class PathEntity(ItemEntity): return self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults self.child_obj.set_override_state(state, ignore_missing_defaults) def update_default_value(self, value): @@ -351,6 +352,7 @@ class ListStrictEntity(ItemEntity): return self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 2225523792..64bbad28a7 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -102,7 +102,9 @@ class ListEntity(EndpointEntity): def add_new_item(self, idx=None, trigger_change=True): child_obj = self._add_new_item(idx) - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, self._ignore_missing_defaults + ) if trigger_change: self.on_child_change(child_obj) @@ -212,6 +214,7 @@ class ListEntity(EndpointEntity): return self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults while self.children: self.children.pop(0) @@ -403,7 +406,9 @@ class ListEntity(EndpointEntity): if self.had_studio_override: child_obj.update_studio_value(item) - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, self._ignore_missing_defaults + ) if self._override_state >= OverrideState.PROJECT: self._has_project_override = self.had_project_override @@ -435,7 +440,9 @@ class ListEntity(EndpointEntity): for item in value: child_obj = self._add_new_item() child_obj.update_default_value(item) - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, self._ignore_missing_defaults + ) self._ignore_child_changes = False @@ -468,7 +475,10 @@ class ListEntity(EndpointEntity): child_obj.update_default_value(item) if self._has_studio_override: child_obj.update_studio_value(item) - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, + self._ignore_missing_defaults + ) self._ignore_child_changes = False From 477b4ecfcc8d421fd4334c87666d61767e049371 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 17:19:13 +0200 Subject: [PATCH 061/110] removed unused imports --- openpype/settings/entities/dict_conditional.py | 3 --- openpype/tools/settings/settings/dict_conditional.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 33cedd7b54..c115cac18a 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -1,8 +1,6 @@ import copy -import collections from .lib import ( - WRAPPER_TYPES, OverrideState, NOT_SET ) @@ -14,7 +12,6 @@ from openpype.settings.constants import ( from . import ( BaseItemEntity, ItemEntity, - BoolEntity, GUIEntity ) from .exceptions import ( diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 84288f7b5b..da2f53497e 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -1,5 +1,4 @@ -import collections -from Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets from .widgets import ( ExpandingWidget, From ca7d91af622636b71adb4da42694b79ab8377409 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 13:53:15 +0200 Subject: [PATCH 062/110] fix typos --- openpype/settings/entities/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 42a08232b9..faaacd4230 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -147,7 +147,7 @@ class SchemasHub: crashed_item = self._crashed_on_load[schema_name] raise KeyError( "Unable to parse schema file \"{}\". {}".format( - crashed_item["filpath"], crashed_item["message"] + crashed_item["filepath"], crashed_item["message"] ) ) @@ -177,7 +177,7 @@ class SchemasHub: crashed_item = self._crashed_on_load[template_name] raise KeyError( "Unable to parse templace file \"{}\". {}".format( - crashed_item["filpath"], crashed_item["message"] + crashed_item["filepath"], crashed_item["message"] ) ) @@ -345,7 +345,7 @@ class SchemasHub: " One of them crashed on load \"{}\" {}" ).format( filename, - crashed_item["filpath"], + crashed_item["filepath"], crashed_item["message"] )) From ad1891e6375a8796b98e8082f7fe577db449877d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 14:55:35 +0200 Subject: [PATCH 063/110] add list version command, fix staging and use-version --- start.py | 90 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/start.py b/start.py index 8e7c195e95..7ce7d6afe7 100644 --- a/start.py +++ b/start.py @@ -135,18 +135,36 @@ if sys.__stdout__: def _print(message: str): if message.startswith("!!! "): print("{}{}".format(term.orangered2("!!! "), message[4:])) + return if message.startswith(">>> "): print("{}{}".format(term.aquamarine3(">>> "), message[4:])) + return if message.startswith("--- "): print("{}{}".format(term.darkolivegreen3("--- "), message[4:])) - if message.startswith(" "): - print("{}{}".format(term.darkseagreen3(" "), message[4:])) + return if message.startswith("*** "): print("{}{}".format(term.gold("*** "), message[4:])) + return if message.startswith(" - "): print("{}{}".format(term.wheat(" - "), message[4:])) + return if message.startswith(" . "): print("{}{}".format(term.tan(" . "), message[4:])) + return + if message.startswith(" - "): + print("{}{}".format(term.seagreen3(" - "), message[7:])) + return + if message.startswith(" ! "): + print("{}{}".format(term.goldenrod(" ! "), message[7:])) + return + if message.startswith(" * "): + print("{}{}".format(term.aquamarine1(" * "), message[7:])) + return + if message.startswith(" "): + print("{}{}".format(term.darkseagreen3(" "), message[4:])) + return + + print(message) else: def _print(message: str): print(message) @@ -175,6 +193,17 @@ silent_commands = ["run", "igniter", "standalonepublisher", "extractenvironments"] +def list_versions(openpype_versions: list, local_version=None) -> None: + """Print list of detected versions.""" + _print(" - Detected versions:") + for v in sorted(openpype_versions): + _print(f" - {v}: {v.path}") + if not openpype_versions: + _print(" ! none in repository detected") + if local_version: + _print(f" * local version {local_version}") + + def set_openpype_global_environments() -> None: """Set global OpenPype's environments.""" import acre @@ -303,6 +332,7 @@ def _process_arguments() -> tuple: # check for `--use-version=3.0.0` argument and `--use-staging` use_version = None use_staging = False + print_versions = False for arg in sys.argv: if arg == "--use-version": _print("!!! Please use option --use-version like:") @@ -313,12 +343,19 @@ def _process_arguments() -> tuple: r"--use-version=(?P\d+\.\d+\.\d+(?:\S*)?)", arg) if m and m.group('version'): use_version = m.group('version') + _print(">>> Requested version [ {} ]".format(use_version)) sys.argv.remove(arg) + if "+staging" in use_version: + use_staging = True break if "--use-staging" in sys.argv: use_staging = True sys.argv.remove("--use-staging") + if "--list-versions" in sys.argv: + print_versions = True + sys.argv.remove("--list-versions") + # handle igniter # this is helper to run igniter before anything else if "igniter" in sys.argv: @@ -334,7 +371,7 @@ def _process_arguments() -> tuple: sys.argv.pop(idx) sys.argv.insert(idx, "tray") - return use_version, use_staging + return use_version, use_staging, print_versions def _determine_mongodb() -> str: @@ -487,7 +524,7 @@ def _find_frozen_openpype(use_version: str = None, openpype_version = openpype_versions[-1] except IndexError: _print(("!!! Something is wrong and we didn't " - "found it again.")) + "found it again.")) sys.exit(1) elif return_code != 2: _print(f" . finished ({return_code})") @@ -519,13 +556,8 @@ def _find_frozen_openpype(use_version: str = None, if found: openpype_version = sorted(found)[-1] if not openpype_version: - _print(f"!!! requested version {use_version} was not found.") - if openpype_versions: - _print(" - found: ") - for v in sorted(openpype_versions): - _print(f" - {v}: {v.path}") - - _print(f" - local version {local_version}") + _print(f"!!! Requested version {use_version} was not found.") + list_versions(openpype_versions, local_version) sys.exit(1) # test if latest detected is installed (in user data dir) @@ -560,7 +592,7 @@ def _find_frozen_openpype(use_version: str = None, return openpype_version.path -def _bootstrap_from_code(use_version): +def _bootstrap_from_code(use_version, use_staging): """Bootstrap live code (or the one coming with frozen OpenPype. Args: @@ -583,7 +615,8 @@ def _bootstrap_from_code(use_version): if use_version and use_version != local_version: version_to_use = None - openpype_versions = bootstrap.find_openpype(include_zips=True) + openpype_versions = bootstrap.find_openpype( + include_zips=True, staging=use_staging) v: OpenPypeVersion found = [v for v in openpype_versions if str(v) == use_version] if found: @@ -600,13 +633,8 @@ def _bootstrap_from_code(use_version): os.environ["OPENPYPE_REPOS_ROOT"] = (version_path / "openpype").as_posix() # noqa: E501 _openpype_root = version_to_use.path.as_posix() else: - _print(f"!!! requested version {use_version} was not found.") - if openpype_versions: - _print(" - found: ") - for v in sorted(openpype_versions): - _print(f" - {v}: {v.path}") - - _print(f" - local version {local_version}") + _print(f"!!! Requested version {use_version} was not found.") + list_versions(openpype_versions, local_version) sys.exit(1) else: os.environ["OPENPYPE_VERSION"] = local_version @@ -675,7 +703,7 @@ def boot(): # Process arguments # ------------------------------------------------------------------------ - use_version, use_staging = _process_arguments() + use_version, use_staging, print_versions = _process_arguments() if os.getenv("OPENPYPE_VERSION"): use_staging = "staging" in os.getenv("OPENPYPE_VERSION") @@ -704,6 +732,24 @@ def boot(): if not os.getenv("OPENPYPE_PATH") and openpype_path: os.environ["OPENPYPE_PATH"] = openpype_path + if print_versions: + if not use_staging: + _print("--- This will list only non-staging versions detected.") + _print(" To see staging versions, use --use-staging argument.") + else: + _print("--- This will list only staging versions detected.") + _print(" To see other version, omit --use-staging argument.") + _openpype_root = OPENPYPE_ROOT + openpype_versions = bootstrap.find_openpype(include_zips=True, + staging=use_staging) + if getattr(sys, 'frozen', False): + local_version = bootstrap.get_version(Path(_openpype_root)) + else: + local_version = bootstrap.get_local_live_version() + + list_versions(openpype_versions, local_version) + sys.exit(1) + # ------------------------------------------------------------------------ # Find OpenPype versions # ------------------------------------------------------------------------ @@ -718,7 +764,7 @@ def boot(): _print(f"!!! {e}") sys.exit(1) else: - version_path = _bootstrap_from_code(use_version) + version_path = _bootstrap_from_code(use_version, use_staging) # set this to point either to `python` from venv in case of live code # or to `openpype` or `openpype_console` in case of frozen code From 8c342f5bd65dde388ed513030b7c49c76ddc6c43 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 14:56:22 +0200 Subject: [PATCH 064/110] fix version resolution --- igniter/bootstrap_repos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 6eaea27116..8c081b8614 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -657,7 +657,7 @@ class BootstrapRepos: ] # remove duplicates - openpype_versions = list(set(openpype_versions)) + openpype_versions = sorted(list(set(openpype_versions))) return openpype_versions From 920b94fa7f4a6cdb8ecdf40751a6e765478a227d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 14:56:41 +0200 Subject: [PATCH 065/110] add more info to tests --- tests/igniter/test_bootstrap_repos.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/tests/igniter/test_bootstrap_repos.py b/tests/igniter/test_bootstrap_repos.py index 743131acfa..740a71a5ce 100644 --- a/tests/igniter/test_bootstrap_repos.py +++ b/tests/igniter/test_bootstrap_repos.py @@ -330,8 +330,8 @@ def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): assert result[-1].path == expected_path, ("not a latest version of " "OpenPype 3") + printer("testing finding OpenPype in OPENPYPE_PATH ...") monkeypatch.setenv("OPENPYPE_PATH", e_path.as_posix()) - result = fix_bootstrap.find_openpype(include_zips=True) # we should have results as file were created assert result is not None, "no OpenPype version found" @@ -349,6 +349,8 @@ def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): monkeypatch.delenv("OPENPYPE_PATH", raising=False) + printer("testing finding OpenPype in user data dir ...") + # mock appdirs user_data_dir def mock_user_data_dir(*args, **kwargs): """Mock local app data dir.""" @@ -373,18 +375,7 @@ def test_find_openpype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): assert result[-1].path == expected_path, ("not a latest version of " "OpenPype 2") - result = fix_bootstrap.find_openpype(e_path, include_zips=True) - assert result is not None, "no OpenPype version found" - expected_path = Path( - e_path / "{}{}{}".format( - test_versions_1[5].prefix, - test_versions_1[5].version, - test_versions_1[5].suffix - ) - ) - assert result[-1].path == expected_path, ("not a latest version of " - "OpenPype 1") - + printer("testing finding OpenPype zip/dir precedence ...") result = fix_bootstrap.find_openpype(dir_path, include_zips=True) assert result is not None, "no OpenPype versions found" expected_path = Path( From 8c034a9a68d2b1f6f1e5453a28c0be50523fcbe6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 15:04:17 +0200 Subject: [PATCH 066/110] add documentation to --list-versions --- website/docs/admin_openpype_commands.md | 2 ++ website/docs/admin_use.md | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/website/docs/admin_openpype_commands.md b/website/docs/admin_openpype_commands.md index 324e0e8481..1a91e2e7fe 100644 --- a/website/docs/admin_openpype_commands.md +++ b/website/docs/admin_openpype_commands.md @@ -21,6 +21,8 @@ openpype_console --use-version=3.0.0-foo+bar `--use-staging` - to use staging versions of OpenPype. +`--list-versions [--use-staging]` - to list available versions. + For more information [see here](admin_use#run-openpype). ## Commands diff --git a/website/docs/admin_use.md b/website/docs/admin_use.md index 4a2b56e6f4..4ad08a0174 100644 --- a/website/docs/admin_use.md +++ b/website/docs/admin_use.md @@ -46,6 +46,16 @@ openpype_console --use-version=3.0.1 `--use-staging` - to specify you prefer staging version. In that case it will be used (if found) instead of production one. +:::tip List available versions +To list all available versions, use: + +```shell +openpype_console --list-versions +``` + +You can add `--use-staging` to list staging versions. +::: + ### Details When you run OpenPype from executable, few check are made: From 07a18a15b2207f46dabf98eb0fc5ab4f095ca081 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 16:08:10 +0200 Subject: [PATCH 067/110] fix argument --- start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.py b/start.py index 7ce7d6afe7..cd6be0a7dd 100644 --- a/start.py +++ b/start.py @@ -537,7 +537,7 @@ def _find_frozen_openpype(use_version: str = None, _print("*** Still no luck finding OpenPype.") _print(("*** We'll try to use the one coming " "with OpenPype installation.")) - version_path = _bootstrap_from_code(use_version) + version_path = _bootstrap_from_code(use_version, use_staging) openpype_version = OpenPypeVersion( version=BootstrapRepos.get_version(version_path), path=version_path) From ad3acb706c0522888bd3d57926da59026a225bd7 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 16:45:47 +0200 Subject: [PATCH 068/110] always create log file during build --- tools/build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build.ps1 b/tools/build.ps1 index c8c2f392ad..89795b0a50 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -185,9 +185,9 @@ Write-Host "Building OpenPype ..." $startTime = [int][double]::Parse((Get-Date -UFormat %s)) $out = & poetry run python setup.py build 2>&1 +Set-Content -Path "$($openpype_root)\build\build.log" -Value $out if ($LASTEXITCODE -ne 0) { - Set-Content -Path "$($openpype_root)\build\build.log" -Value $out Write-Host "!!! " -NoNewLine -ForegroundColor Red Write-Host "Build failed. Check the log: " -NoNewline Write-Host ".\build\build.log" -ForegroundColor Yellow From c2b6f199c61a4e66e0cdd963a80d6967038eca4f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 16:46:52 +0200 Subject: [PATCH 069/110] fail if invalid version format requested --- start.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/start.py b/start.py index cd6be0a7dd..b2fff4eac0 100644 --- a/start.py +++ b/start.py @@ -348,6 +348,12 @@ def _process_arguments() -> tuple: if "+staging" in use_version: use_staging = True break + else: + _print("!!! Requested version isn't in correct format.") + _print((" Use --list-versions to find out" + " proper version string.")) + sys.exit(1) + if "--use-staging" in sys.argv: use_staging = True sys.argv.remove("--use-staging") @@ -607,7 +613,8 @@ def _bootstrap_from_code(use_version, use_staging): _openpype_root = OPENPYPE_ROOT if getattr(sys, 'frozen', False): local_version = bootstrap.get_version(Path(_openpype_root)) - _print(f" - running version: {local_version}") + switch_str = f" - will switch to {use_version}" if use_version else "" + _print(f" - booting version: {local_version}{switch_str}") assert local_version else: # get current version of OpenPype @@ -706,8 +713,13 @@ def boot(): use_version, use_staging, print_versions = _process_arguments() if os.getenv("OPENPYPE_VERSION"): - use_staging = "staging" in os.getenv("OPENPYPE_VERSION") - use_version = os.getenv("OPENPYPE_VERSION") + if use_version: + _print(("*** environment variable OPENPYPE_VERSION" + "is overridden by command line argument.")) + else: + _print(">>> version set by environment variable") + use_staging = "staging" in os.getenv("OPENPYPE_VERSION") + use_version = os.getenv("OPENPYPE_VERSION") # ------------------------------------------------------------------------ # Determine mongodb connection From 3b71660224f440e280671d4d4d43ef0b79a7ecbd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 16:49:20 +0200 Subject: [PATCH 070/110] fix staging info --- start.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/start.py b/start.py index b2fff4eac0..0b8528bade 100644 --- a/start.py +++ b/start.py @@ -812,7 +812,7 @@ def boot(): from openpype.version import __version__ assert version_path, "Version path not defined." - info = get_info() + info = get_info(use_staging) info.insert(0, f">>> Using OpenPype from [ {version_path} ]") t_width = 20 @@ -839,7 +839,7 @@ def boot(): sys.exit(1) -def get_info() -> list: +def get_info(use_staging=None) -> list: """Print additional information to console.""" from openpype.lib.mongo import get_default_components from openpype.lib.log import PypeLogger @@ -847,7 +847,7 @@ def get_info() -> list: components = get_default_components() inf = [] - if not getattr(sys, 'frozen', False): + if use_staging: inf.append(("OpenPype variant", "staging")) else: inf.append(("OpenPype variant", "production")) From bbd6e1f24e6b9f0cd52823408f01b9ccbd26e433 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 Jul 2021 17:02:55 +0200 Subject: [PATCH 071/110] fix processing of `--use-version` --- start.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/start.py b/start.py index 0b8528bade..44f8e1b0da 100644 --- a/start.py +++ b/start.py @@ -339,20 +339,21 @@ def _process_arguments() -> tuple: _print(" --use-version=3.0.0") sys.exit(1) - m = re.search( - r"--use-version=(?P\d+\.\d+\.\d+(?:\S*)?)", arg) - if m and m.group('version'): - use_version = m.group('version') - _print(">>> Requested version [ {} ]".format(use_version)) - sys.argv.remove(arg) - if "+staging" in use_version: - use_staging = True - break - else: - _print("!!! Requested version isn't in correct format.") - _print((" Use --list-versions to find out" - " proper version string.")) - sys.exit(1) + if arg.startswith("--use-version="): + m = re.search( + r"--use-version=(?P\d+\.\d+\.\d+(?:\S*)?)", arg) + if m and m.group('version'): + use_version = m.group('version') + _print(">>> Requested version [ {} ]".format(use_version)) + sys.argv.remove(arg) + if "+staging" in use_version: + use_staging = True + break + else: + _print("!!! Requested version isn't in correct format.") + _print((" Use --list-versions to find out" + " proper version string.")) + sys.exit(1) if "--use-staging" in sys.argv: use_staging = True From 3ead357d79d029371d7e368bea633e6af0a80a36 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 3 Jul 2021 03:41:04 +0000 Subject: [PATCH 072/110] [Automated] Bump version --- CHANGELOG.md | 16 ++++++++++++---- openpype/version.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b69a8e2d5..4e658d6995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,18 @@ # Changelog -## [3.2.0-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.2.0-nightly.6](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.4...HEAD) **🚀 Enhancements** +- Settings application use python 2 only where needed [\#1776](https://github.com/pypeclub/OpenPype/pull/1776) - Settings UI copy/paste [\#1769](https://github.com/pypeclub/OpenPype/pull/1769) - Workfile tool widths [\#1766](https://github.com/pypeclub/OpenPype/pull/1766) - Push hierarchical attributes care about task parent changes [\#1763](https://github.com/pypeclub/OpenPype/pull/1763) - Application executables with environment variables [\#1757](https://github.com/pypeclub/OpenPype/pull/1757) +- Deadline: Nuke submission additional attributes [\#1756](https://github.com/pypeclub/OpenPype/pull/1756) +- Settings schema without prefill [\#1753](https://github.com/pypeclub/OpenPype/pull/1753) - Settings Hosts enum [\#1739](https://github.com/pypeclub/OpenPype/pull/1739) - Validate containers settings [\#1736](https://github.com/pypeclub/OpenPype/pull/1736) - PS - added loader from sequence [\#1726](https://github.com/pypeclub/OpenPype/pull/1726) @@ -22,7 +25,9 @@ **🐛 Bug fixes** +- Otio unrelated error on import [\#1782](https://github.com/pypeclub/OpenPype/pull/1782) - FFprobe streams order [\#1775](https://github.com/pypeclub/OpenPype/pull/1775) +- Fix - single file files are str only, cast it to list to count properly [\#1772](https://github.com/pypeclub/OpenPype/pull/1772) - Project specific environments [\#1767](https://github.com/pypeclub/OpenPype/pull/1767) - Settings UI with refresh button [\#1764](https://github.com/pypeclub/OpenPype/pull/1764) - Standalone publisher thumbnail extractor fix [\#1761](https://github.com/pypeclub/OpenPype/pull/1761) @@ -46,7 +51,7 @@ - Bump prismjs from 1.23.0 to 1.24.0 in /website [\#1773](https://github.com/pypeclub/OpenPype/pull/1773) - TVPaint ftrack family [\#1755](https://github.com/pypeclub/OpenPype/pull/1755) -- global: removing obsolete ftrack validator plugin [\#1710](https://github.com/pypeclub/OpenPype/pull/1710) +- Sync main 2.x back to 2.x develop [\#1715](https://github.com/pypeclub/OpenPype/pull/1715) ## [2.18.4](https://github.com/pypeclub/OpenPype/tree/2.18.4) (2021-06-24) @@ -61,6 +66,10 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.2.0-nightly.2...2.18.3) +**🚀 Enhancements** + +- Log Viewer with OpenPype style [\#1703](https://github.com/pypeclub/OpenPype/pull/1703) + **🐛 Bug fixes** - Tools names forwards compatibility [\#1727](https://github.com/pypeclub/OpenPype/pull/1727) @@ -88,7 +97,6 @@ **🚀 Enhancements** -- Log Viewer with OpenPype style [\#1703](https://github.com/pypeclub/OpenPype/pull/1703) - Scrolling in OpenPype info widget [\#1702](https://github.com/pypeclub/OpenPype/pull/1702) - OpenPype style in modules [\#1694](https://github.com/pypeclub/OpenPype/pull/1694) - Sort applications and tools alphabetically in Settings UI [\#1689](https://github.com/pypeclub/OpenPype/pull/1689) @@ -116,10 +124,10 @@ - Transparent branches in view on Mac [\#1648](https://github.com/pypeclub/OpenPype/pull/1648) - Add asset on task item [\#1646](https://github.com/pypeclub/OpenPype/pull/1646) - Project manager save and queue [\#1645](https://github.com/pypeclub/OpenPype/pull/1645) -- New project anatomy values [\#1644](https://github.com/pypeclub/OpenPype/pull/1644) **Merged pull requests:** +- global: removing obsolete ftrack validator plugin [\#1710](https://github.com/pypeclub/OpenPype/pull/1710) - update dependencies [\#1697](https://github.com/pypeclub/OpenPype/pull/1697) - Bump normalize-url from 4.5.0 to 4.5.1 in /website [\#1686](https://github.com/pypeclub/OpenPype/pull/1686) diff --git a/openpype/version.py b/openpype/version.py index 0371d5f4e3..86d62a83d0 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.2.0-nightly.5" +__version__ = "3.2.0-nightly.6" From ec0a57f87667d583b55d826e9b1a06b6c94f0c68 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 10:41:37 +0200 Subject: [PATCH 073/110] make sure email is not None but string --- .../event_handlers_server/action_clone_review_session.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py index d29316c795..e165466d00 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py @@ -16,11 +16,12 @@ def clone_review_session(session, entity): # Add all invitees. for invitee in entity["review_session_invitees"]: + email = invitee["email"] or "" session.create( "ReviewSessionInvitee", { "name": invitee["name"], - "email": invitee["email"], + "email": email, "review_session": review_session } ) From 3d086c967a0dd79c5c320bf4a552e2de8dab9425 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 10:46:11 +0200 Subject: [PATCH 074/110] added comment --- .../ftrack/event_handlers_server/action_clone_review_session.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py index e165466d00..59c8bffb75 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py @@ -16,6 +16,7 @@ def clone_review_session(session, entity): # Add all invitees. for invitee in entity["review_session_invitees"]: + # Make sure email is not None but string email = invitee["email"] or "" session.create( "ReviewSessionInvitee", From 11da3d0d4dc26992e8dd2fcd4344d46aa19a57f6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 7 Jul 2021 12:12:11 +0200 Subject: [PATCH 075/110] add `--list-versions` help --- openpype/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/cli.py b/openpype/cli.py index 48951c7287..ec5b04c468 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -15,6 +15,9 @@ from .pype_commands import PypeCommands expose_value=False, help="use specified version") @click.option("--use-staging", is_flag=True, expose_value=False, help="use staging variants") +@click.option("--list-versions", is_flag=True, expose_value=False, + help=("list all detected versions. Use With `--use-staging " + "to list staging versions.")) def main(ctx): """Pype is main command serving as entry point to pipeline system. From 9d8b3d222442200b15f770c01ee80ef43c80f468 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 7 Jul 2021 12:46:32 +0200 Subject: [PATCH 076/110] fix standalone `--use-staging` --- start.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/start.py b/start.py index 44f8e1b0da..1b5c25ae3a 100644 --- a/start.py +++ b/start.py @@ -621,10 +621,26 @@ def _bootstrap_from_code(use_version, use_staging): # get current version of OpenPype local_version = bootstrap.get_local_live_version() - if use_version and use_version != local_version: - version_to_use = None - openpype_versions = bootstrap.find_openpype( - include_zips=True, staging=use_staging) + version_to_use = None + openpype_versions = bootstrap.find_openpype( + include_zips=True, staging=use_staging) + if use_staging and not use_version: + try: + version_to_use = openpype_versions[-1] + except IndexError: + _print("!!! No staging versions are found.") + list_versions(openpype_versions, local_version) + sys.exit(1) + if version_to_use.path.is_file(): + version_to_use.path = bootstrap.extract_openpype( + version_to_use) + bootstrap.add_paths_from_directory(version_to_use.path) + os.environ["OPENPYPE_VERSION"] = str(version_to_use) + version_path = version_to_use.path + os.environ["OPENPYPE_REPOS_ROOT"] = (version_path / "openpype").as_posix() # noqa: E501 + _openpype_root = version_to_use.path.as_posix() + + elif use_version and use_version != local_version: v: OpenPypeVersion found = [v for v in openpype_versions if str(v) == use_version] if found: From f0e6bb7a82a98e040f47087bdb00fc7ffed47cbc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 17:42:30 +0200 Subject: [PATCH 077/110] created copy of multiple notes action --- .../action_multiple_notes.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py new file mode 100644 index 0000000000..8db65fe39b --- /dev/null +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -0,0 +1,110 @@ +from openpype.modules.ftrack.lib import BaseAction, statics_icon + + +class MultipleNotes(BaseAction): + '''Edit meta data action.''' + + #: Action identifier. + identifier = 'multiple.notes' + #: Action label. + label = 'Multiple Notes' + #: Action description. + description = 'Add same note to multiple Asset Versions' + icon = statics_icon("ftrack", "action_icons", "MultipleNotes.svg") + + def discover(self, session, entities, event): + ''' Validation ''' + valid = True + for entity in entities: + if entity.entity_type.lower() != 'assetversion': + valid = False + break + return valid + + def interface(self, session, entities, event): + if not event['data'].get('values', {}): + note_label = { + 'type': 'label', + 'value': '# Enter note: #' + } + + note_value = { + 'name': 'note', + 'type': 'textarea' + } + + category_label = { + 'type': 'label', + 'value': '## Category: ##' + } + + category_data = [] + category_data.append({ + 'label': '- None -', + 'value': 'none' + }) + all_categories = session.query('NoteCategory').all() + for cat in all_categories: + category_data.append({ + 'label': cat['name'], + 'value': cat['id'] + }) + category_value = { + 'type': 'enumerator', + 'name': 'category', + 'data': category_data, + 'value': 'none' + } + + splitter = { + 'type': 'label', + 'value': '{}'.format(200*"-") + } + + items = [] + items.append(note_label) + items.append(note_value) + items.append(splitter) + items.append(category_label) + items.append(category_value) + return items + + def launch(self, session, entities, event): + if 'values' not in event['data']: + return + + values = event['data']['values'] + if len(values) <= 0 or 'note' not in values: + return False + # Get Note text + note_value = values['note'] + if note_value.lower().strip() == '': + return False + # Get User + user = session.query( + 'User where username is "{}"'.format(session.api_user) + ).one() + # Base note data + note_data = { + 'content': note_value, + 'author': user + } + # Get category + category_value = values['category'] + if category_value != 'none': + category = session.query( + 'NoteCategory where id is "{}"'.format(category_value) + ).one() + note_data['category'] = category + # Create notes for entities + for entity in entities: + new_note = session.create('Note', note_data) + entity['notes'].append(new_note) + session.commit() + return True + + +def register(session): + '''Register plugin. Called when used as an plugin.''' + + MultipleNotes(session).register() From 4a2efb1cd2ae9b617ceaf8ddd45eb1b304e7de2f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 17:53:06 +0200 Subject: [PATCH 078/110] Converted to server action --- .../action_multiple_notes.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index 8db65fe39b..021a61e0ce 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -1,16 +1,12 @@ -from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import ServerAction -class MultipleNotes(BaseAction): - '''Edit meta data action.''' +class MultipleNotesServer(ServerAction): + """Action adds same note for muliple AssetVersions.""" - #: Action identifier. - identifier = 'multiple.notes' - #: Action label. - label = 'Multiple Notes' - #: Action description. - description = 'Add same note to multiple Asset Versions' - icon = statics_icon("ftrack", "action_icons", "MultipleNotes.svg") + identifier = "multiple.notes.server" + label = "Multiple Notes (Server)" + description = "Add same note to multiple Asset Versions" def discover(self, session, entities, event): ''' Validation ''' @@ -107,4 +103,4 @@ class MultipleNotes(BaseAction): def register(session): '''Register plugin. Called when used as an plugin.''' - MultipleNotes(session).register() + MultipleNotesServer(session).register() From c1994950adb7cc28704f409c3486b5a875d36311 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 17:54:07 +0200 Subject: [PATCH 079/110] use user from event instead of session event --- .../action_multiple_notes.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index 021a61e0ce..c41b900031 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -18,6 +18,15 @@ class MultipleNotesServer(ServerAction): return valid def interface(self, session, entities, event): + event_source = event["source"] + user_info = event_source.get("user") or {} + user_id = user_info.get("id") + if not user_id: + return { + "success": False, + "message": "Couldn't get user information." + } + if not event['data'].get('values', {}): note_label = { 'type': 'label', @@ -77,9 +86,21 @@ class MultipleNotesServer(ServerAction): if note_value.lower().strip() == '': return False # Get User - user = session.query( - 'User where username is "{}"'.format(session.api_user) - ).one() + event_source = event["source"] + user_info = event_source.get("user") or {} + user_id = user_info.get("id") + user = None + if user_id: + user = session.query( + 'User where id is "{}"'.format(user_id) + ).first() + + if not user: + return { + "success": False, + "message": "Couldn't get user information." + } + # Base note data note_data = { 'content': note_value, From 2710bce4bd1d69ef20c8867db8db13f3dfb2f0be Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 17:57:46 +0200 Subject: [PATCH 080/110] use constant for none category --- .../ftrack/event_handlers_server/action_multiple_notes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index c41b900031..64b5161366 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -8,6 +8,8 @@ class MultipleNotesServer(ServerAction): label = "Multiple Notes (Server)" description = "Add same note to multiple Asset Versions" + _none_category = "__NONE__" + def discover(self, session, entities, event): ''' Validation ''' valid = True @@ -46,7 +48,7 @@ class MultipleNotesServer(ServerAction): category_data = [] category_data.append({ 'label': '- None -', - 'value': 'none' + "value": self._none_category }) all_categories = session.query('NoteCategory').all() for cat in all_categories: @@ -58,7 +60,7 @@ class MultipleNotesServer(ServerAction): 'type': 'enumerator', 'name': 'category', 'data': category_data, - 'value': 'none' + "value": self._none_category } splitter = { @@ -108,7 +110,7 @@ class MultipleNotesServer(ServerAction): } # Get category category_value = values['category'] - if category_value != 'none': + if category_value != self._none_category: category = session.query( 'NoteCategory where id is "{}"'.format(category_value) ).one() From 61588d9ddd317213dd0795c69b8fd8f22ee898ea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 17:58:56 +0200 Subject: [PATCH 081/110] formatting changes --- .../action_multiple_notes.py | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index 64b5161366..d08e66ff56 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -14,7 +14,7 @@ class MultipleNotesServer(ServerAction): ''' Validation ''' valid = True for entity in entities: - if entity.entity_type.lower() != 'assetversion': + if entity.entity_type.lower() != "assetversion": valid = False break return valid @@ -31,62 +31,64 @@ class MultipleNotesServer(ServerAction): if not event['data'].get('values', {}): note_label = { - 'type': 'label', - 'value': '# Enter note: #' + "type": "label", + "value": "# Enter note: #" } note_value = { - 'name': 'note', - 'type': 'textarea' + "name": "note", + "type": "textarea" } category_label = { - 'type': 'label', - 'value': '## Category: ##' + "type": "label", + "value": "## Category: ##" } category_data = [] category_data.append({ - 'label': '- None -', + "label": "- None -", "value": self._none_category }) all_categories = session.query('NoteCategory').all() for cat in all_categories: category_data.append({ - 'label': cat['name'], - 'value': cat['id'] + "label": cat["name"], + "value": cat["id"] }) category_value = { - 'type': 'enumerator', - 'name': 'category', - 'data': category_data, + "type": "enumerator", + "name": "category", + "data": category_data, "value": self._none_category } splitter = { - 'type': 'label', - 'value': '{}'.format(200*"-") + "type": "label", + "value": "---" } - items = [] - items.append(note_label) - items.append(note_value) - items.append(splitter) - items.append(category_label) - items.append(category_value) - return items + return [ + note_label, + note_value, + splitter, + category_label, + category_value + ] def launch(self, session, entities, event): - if 'values' not in event['data']: + if "values" not in event["data"]: return - values = event['data']['values'] - if len(values) <= 0 or 'note' not in values: + values = event["data"]["values"] + if len(values) <= 0 or "note" not in values: return False + # Get Note text - note_value = values['note'] - if note_value.lower().strip() == '': + note_value = values["note"] + if note_value.lower().strip() == "": return False + # Get User event_source = event["source"] user_info = event_source.get("user") or {} @@ -105,20 +107,20 @@ class MultipleNotesServer(ServerAction): # Base note data note_data = { - 'content': note_value, - 'author': user + "content": note_value, + "author": user } # Get category - category_value = values['category'] + category_value = values["category"] if category_value != self._none_category: category = session.query( - 'NoteCategory where id is "{}"'.format(category_value) + "NoteCategory where id is \"{}\"".format(category_value) ).one() - note_data['category'] = category + note_data["category"] = category # Create notes for entities for entity in entities: - new_note = session.create('Note', note_data) - entity['notes'].append(new_note) + new_note = session.create("Note", note_data) + entity["notes"].append(new_note) session.commit() return True From 52556fc9cea02ce331ba59ef556ccf6bb2f7dbaf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 17:59:28 +0200 Subject: [PATCH 082/110] reversed logic of interface --- .../action_multiple_notes.py | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index d08e66ff56..7b8e883174 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -29,52 +29,57 @@ class MultipleNotesServer(ServerAction): "message": "Couldn't get user information." } - if not event['data'].get('values', {}): - note_label = { - "type": "label", - "value": "# Enter note: #" - } + values = event["data"].get("values") + if values: + return None - note_value = { - "name": "note", - "type": "textarea" - } + note_label = { + "type": "label", + "value": "# Enter note: #" + } - category_label = { - "type": "label", - "value": "## Category: ##" - } + note_value = { + "name": "note", + "type": "textarea" + } - category_data = [] + category_label = { + "type": "label", + "value": "## Category: ##" + } + + category_data = [] + category_data.append({ + "label": "- None -", + "value": self._none_category + }) + all_categories = session.query( + "select id, name from NoteCategory" + ).all() + for cat in all_categories: category_data.append({ - "label": "- None -", - "value": self._none_category + "label": cat["name"], + "value": cat["id"] }) - all_categories = session.query('NoteCategory').all() - for cat in all_categories: - category_data.append({ - "label": cat["name"], - "value": cat["id"] - }) - category_value = { - "type": "enumerator", - "name": "category", - "data": category_data, - "value": self._none_category - } + category_value = { + "type": "enumerator", + "name": "category", + "data": category_data, + "value": self._none_category + } - splitter = { - "type": "label", - "value": "---" - } + splitter = { + "type": "label", + "value": "---" + } - return [ - note_label, - note_value, - splitter, - category_label, - category_value - ] + return [ + note_label, + note_value, + splitter, + category_label, + category_value + ] def launch(self, session, entities, event): if "values" not in event["data"]: From 17a97044b9bef033aefc392f7a3aa8011de0be6f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 18:23:21 +0200 Subject: [PATCH 083/110] addedd few modifications to discovery --- .../event_handlers_server/action_multiple_notes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index 7b8e883174..7ed0129951 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -11,13 +11,14 @@ class MultipleNotesServer(ServerAction): _none_category = "__NONE__" def discover(self, session, entities, event): - ''' Validation ''' - valid = True + """Show action only on AssetVersions.""" + if not entities: + return False + for entity in entities: if entity.entity_type.lower() != "assetversion": - valid = False - break - return valid + return False + return True def interface(self, session, entities, event): event_source = event["source"] From baf02590c24c27961ce60657f4901d894c538a06 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 18:23:50 +0200 Subject: [PATCH 084/110] fixed few return values --- .../event_handlers_server/action_multiple_notes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index 7ed0129951..1c3c9929d9 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -25,10 +25,7 @@ class MultipleNotesServer(ServerAction): user_info = event_source.get("user") or {} user_id = user_info.get("id") if not user_id: - return { - "success": False, - "message": "Couldn't get user information." - } + return None values = event["data"].get("values") if values: @@ -84,7 +81,7 @@ class MultipleNotesServer(ServerAction): def launch(self, session, entities, event): if "values" not in event["data"]: - return + return None values = event["data"]["values"] if len(values) <= 0 or "note" not in values: @@ -93,7 +90,10 @@ class MultipleNotesServer(ServerAction): # Get Note text note_value = values["note"] if note_value.lower().strip() == "": - return False + return { + "success": True, + "message": "Note was not entered. Skipping" + } # Get User event_source = event["source"] From cd3cd88dfacef90a8b0258291ad907d5f7f7ed90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 18:25:07 +0200 Subject: [PATCH 085/110] added debug log --- .../action_multiple_notes.py | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index 1c3c9929d9..340dd659af 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -111,18 +111,44 @@ class MultipleNotesServer(ServerAction): "message": "Couldn't get user information." } + # Logging message preparation + # - username + username = user.get("username") or "N/A" + + # - AssetVersion ids + asset_version_ids_str = ",".join([entity["id"] for entity in entities]) + # Base note data note_data = { "content": note_value, "author": user } + # Get category - category_value = values["category"] - if category_value != self._none_category: + category_id = values["category"] + if category_id == self._none_category: + category_id = None + + category_name = None + if category_id is not None: category = session.query( - "NoteCategory where id is \"{}\"".format(category_value) - ).one() - note_data["category"] = category + "select id, name from NoteCategory where id is \"{}\"".format( + category_id + ) + ).first() + if category: + note_data["category"] = category + category_name = category["name"] + + category_msg = "" + if category_name: + category_msg = " with category: \"{}\"".format(category_name) + + self.log.warning(( + "Creating note{} as User \"{}\" on " + "AssetVersions: {} with value \"{}\"" + ).format(category_msg, username, asset_version_ids_str, note_value)) + # Create notes for entities for entity in entities: new_note = session.create("Note", note_data) From 2be9dd55ee5a52bc0efb7ec46a87d721bd0d9ee0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 18:25:14 +0200 Subject: [PATCH 086/110] enhanced docstring --- .../ftrack/event_handlers_server/action_multiple_notes.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py index 340dd659af..9ad7b1a969 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_server/action_multiple_notes.py @@ -2,7 +2,11 @@ from openpype.modules.ftrack.lib import ServerAction class MultipleNotesServer(ServerAction): - """Action adds same note for muliple AssetVersions.""" + """Action adds same note for muliple AssetVersions. + + Note is added to selection of AssetVersions. Note is created with user + who triggered the action. It is possible to define note category of note. + """ identifier = "multiple.notes.server" label = "Multiple Notes (Server)" From afdb5a8f59d252d0f9f6938896820472ad15b891 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 7 Jul 2021 19:17:34 +0200 Subject: [PATCH 087/110] add pluginInfo and jobInfo custom settings --- .../plugins/publish/submit_maya_deadline.py | 19 +++++++++++++++++++ .../defaults/project_settings/maya.json | 4 ++++ .../schemas/schema_maya_publish.json | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index a5841f406c..0e09641200 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -271,6 +271,21 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): ["DEADLINE_REST_URL"] ) + self._job_info = ( + context.data["project_settings"] + ["maya"] + ["publish"] + ["deadline"] + ["jobInfo"] + ) + self._plugin_info = ( + context.data["project_settings"] + ["maya"] + ["publish"] + ["deadline"] + ["pluginInfo"] + ) + assert self._deadline_url, "Requires DEADLINE_REST_URL" context = instance.context @@ -536,6 +551,10 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): self.preflight_check(instance) + # add jobInfo and pluginInfo variables from Settings + payload["JobInfo"].update(self._job_info) + payload["PluginInfo"].update(self._plugin_info) + # Prepare tiles data ------------------------------------------------ if instance.data.get("tileRendering"): # if we have sequence of files, we need to create tile job for diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 284a1a0040..62d8a74670 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -391,6 +391,10 @@ } } }, + "deadline": { + "jobInfo": {}, + "pluginInfo": {} + }, "ExtractCameraAlembic": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 5ca7059ee5..7e50682f5d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -309,6 +309,24 @@ "type": "schema", "name": "schema_maya_capture" }, + { + "type": "dict", + "collapsible": true, + "key": "deadline", + "label": "Additional Deadline Settings", + "children": [ + { + "type": "raw-json", + "key": "jobInfo", + "label": "Additional JobInfo data" + }, + { + "type": "raw-json", + "key": "pluginInfo", + "label": "Additional PluginInfo data" + } + ] + }, { "type": "dict", "collapsible": true, From 898157c95be9205d77934a03d5c02a7b658e3978 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 19:28:12 +0200 Subject: [PATCH 088/110] fixed typo --- openpype/settings/entities/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index faaacd4230..e58281644a 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -176,7 +176,7 @@ class SchemasHub: elif template_name in self._crashed_on_load: crashed_item = self._crashed_on_load[template_name] raise KeyError( - "Unable to parse templace file \"{}\". {}".format( + "Unable to parse template file \"{}\". {}".format( crashed_item["filepath"], crashed_item["message"] ) ) From ec01e148e56cc17b3b50dfe811e5cf27d4be07ef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 19:36:32 +0200 Subject: [PATCH 089/110] added missing attributes --- openpype/settings/entities/dict_conditional.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index c115cac18a..641986491c 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -144,6 +144,11 @@ class DictConditionalEntity(ItemEntity): self.enum_entity = None + self.highlight_content = self.schema_data.get( + "highlight_content", False + ) + self.show_borders = self.schema_data.get("show_borders", True) + self._add_children() @property From 1cb8d0f5e8b64f9d6990deebb103ddbf920eb987 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 19:37:43 +0200 Subject: [PATCH 090/110] added example of conditional dictionary --- .../schemas/system_schema/example_schema.json | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/openpype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json index a4ed56df32..c3287d7452 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -9,6 +9,54 @@ "label": "Color input", "type": "color" }, + { + "type": "dict-conditional", + "use_label_wrap": true, + "collapsible": true, + "key": "menu_items", + "label": "Menu items", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "command", + "label": "Comand" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": "text" + } + ] + }, + { + "key": "separator", + "label": "Separator" + } + ] + }, { "type": "dict", "key": "schema_template_exaples", From e8bdd1616c5aade342b02d0029d88162c9a294f6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 20:34:13 +0200 Subject: [PATCH 091/110] create QSettings in standalone publisher --- openpype/tools/standalonepublish/app.py | 6 +++++- openpype/tools/standalonepublish/widgets/widget_asset.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 169abe530a..7c3e902f6c 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -34,6 +34,8 @@ class Window(QtWidgets.QDialog): self._db = AvalonMongoDB() self._db.install() + self._settings = QtCore.QSettings("pypeclub", "StandalonePublisher") + self.pyblish_paths = pyblish_paths self.setWindowTitle("Standalone Publish") @@ -44,7 +46,9 @@ class Window(QtWidgets.QDialog): self.valid_parent = False # assets widget - widget_assets = AssetWidget(dbcon=self._db, parent=self) + widget_assets = AssetWidget( + self._settings, dbcon=self._db, parent=self + ) # family widget widget_family = FamilyWidget(dbcon=self._db, parent=self) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 4680e88344..24b93c8343 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -127,11 +127,12 @@ class AssetWidget(QtWidgets.QWidget): current_changed = QtCore.Signal() # on view current index change task_changed = QtCore.Signal() - def __init__(self, dbcon, parent=None): + def __init__(self, settings, dbcon, parent=None): super(AssetWidget, self).__init__(parent=parent) self.setContentsMargins(0, 0, 0, 0) self.dbcon = dbcon + self._settings = settings layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) From 45a6fa1ea5ab83fc6e82a6d60fea00f76990dc63 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 20:35:28 +0200 Subject: [PATCH 092/110] store projects on project change to settings --- .../tools/standalonepublish/widgets/widget_asset.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 24b93c8343..683b05d836 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -255,6 +255,16 @@ class AssetWidget(QtWidgets.QWidget): project_name = self.combo_projects.currentText() if project_name in projects: self.dbcon.Session["AVALON_PROJECT"] = project_name + last_projects = [ + value + for value in self._settings.value("projects", "").split("|") + ] + if project_name in last_projects: + last_projects.remove(project_name) + last_projects.insert(0, project_name) + while len(last_projects) > 5: + last_projects.pop(-1) + self._settings.setValue("projects", "|".join(last_projects)) self.project_changed.emit(project_name) From 07abf855ddb40cac1ade2bc824f1784dd6f6d840 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 20:35:42 +0200 Subject: [PATCH 093/110] load last projects on initialization of projects combobox --- .../standalonepublish/widgets/widget_asset.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 683b05d836..0070488b3e 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -239,14 +239,31 @@ class AssetWidget(QtWidgets.QWidget): return output def _set_projects(self): - projects = list() + project_names = list() for project in self.dbcon.projects(): - projects.append(project['name']) + project_name = project.get("name") + if project_name: + project_names.append(project_name) self.combo_projects.clear() - if len(projects) > 0: - self.combo_projects.addItems(projects) - self.dbcon.Session["AVALON_PROJECT"] = projects[0] + + if not project_names: + return + + sorted_project_names = list(sorted(project_names)) + self.combo_projects.addItems(list(sorted(sorted_project_names))) + + last_projects = self._settings.value("projects", "") + last_project = sorted_project_names[0] + for project_name in last_projects.split("|"): + if project_name in sorted_project_names: + last_project = project_name + break + + index = sorted_project_names.index(last_project) + self.combo_projects.setCurrentIndex(index) + + self.dbcon.Session["AVALON_PROJECT"] = last_project def on_project_change(self): projects = list() From 72ec36239b2ad223e9e2ea64c58d17f51988715e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 20:52:00 +0200 Subject: [PATCH 094/110] load and store last projects is more secure --- openpype/tools/standalonepublish/app.py | 11 +++-- .../standalonepublish/widgets/widget_asset.py | 44 +++++++++++++------ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 7c3e902f6c..81a53c52b8 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -34,7 +34,12 @@ class Window(QtWidgets.QDialog): self._db = AvalonMongoDB() self._db.install() - self._settings = QtCore.QSettings("pypeclub", "StandalonePublisher") + try: + settings = QtCore.QSettings("pypeclub", "StandalonePublisher") + except Exception: + settings = None + + self._settings = settings self.pyblish_paths = pyblish_paths @@ -46,9 +51,7 @@ class Window(QtWidgets.QDialog): self.valid_parent = False # assets widget - widget_assets = AssetWidget( - self._settings, dbcon=self._db, parent=self - ) + widget_assets = AssetWidget(self._db, settings, self) # family widget widget_family = FamilyWidget(dbcon=self._db, parent=self) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 0070488b3e..8fb0d452bd 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -127,7 +127,7 @@ class AssetWidget(QtWidgets.QWidget): current_changed = QtCore.Signal() # on view current index change task_changed = QtCore.Signal() - def __init__(self, settings, dbcon, parent=None): + def __init__(self, dbcon, settings, parent=None): super(AssetWidget, self).__init__(parent=parent) self.setContentsMargins(0, 0, 0, 0) @@ -238,6 +238,34 @@ class AssetWidget(QtWidgets.QWidget): output.extend(self.get_parents(parent)) return output + def _get_last_projects(self): + if not self._settings: + return [] + + project_names = [] + for project_name in self._settings.value("projects", "").split("|"): + if project_name: + project_names.append(project_name) + return project_names + + def _add_last_project(self, project_name): + if not self._settings: + return + + last_projects = [] + for _project_name in self._settings.value("projects", "").split("|"): + if _project_name: + last_projects.append(_project_name) + + if project_name in last_projects: + last_projects.remove(project_name) + + last_projects.insert(0, project_name) + while len(last_projects) > 5: + last_projects.pop(-1) + + self._settings.setValue("projects", "|".join(last_projects)) + def _set_projects(self): project_names = list() for project in self.dbcon.projects(): @@ -253,9 +281,8 @@ class AssetWidget(QtWidgets.QWidget): sorted_project_names = list(sorted(project_names)) self.combo_projects.addItems(list(sorted(sorted_project_names))) - last_projects = self._settings.value("projects", "") last_project = sorted_project_names[0] - for project_name in last_projects.split("|"): + for project_name in self._get_last_projects(): if project_name in sorted_project_names: last_project = project_name break @@ -272,16 +299,7 @@ class AssetWidget(QtWidgets.QWidget): project_name = self.combo_projects.currentText() if project_name in projects: self.dbcon.Session["AVALON_PROJECT"] = project_name - last_projects = [ - value - for value in self._settings.value("projects", "").split("|") - ] - if project_name in last_projects: - last_projects.remove(project_name) - last_projects.insert(0, project_name) - while len(last_projects) > 5: - last_projects.pop(-1) - self._settings.setValue("projects", "|".join(last_projects)) + self._add_last_project(project_name) self.project_changed.emit(project_name) From f973833153676dc4dc39e43d530eb1d96e8af4f5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 20:54:21 +0200 Subject: [PATCH 095/110] added delegate to project combobox --- openpype/tools/standalonepublish/widgets/widget_asset.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 8fb0d452bd..c39d71b055 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -140,6 +140,10 @@ class AssetWidget(QtWidgets.QWidget): # Project self.combo_projects = QtWidgets.QComboBox() + # Change delegate so stylysheets are applied + project_delegate = QtWidgets.QStyledItemDelegate(self.combo_projects) + self.combo_projects.setItemDelegate(project_delegate) + self._set_projects() self.combo_projects.currentTextChanged.connect(self.on_project_change) # Tree View @@ -199,6 +203,7 @@ class AssetWidget(QtWidgets.QWidget): self.selection_changed.connect(self._refresh_tasks) + self.project_delegate = project_delegate self.task_view = task_view self.task_model = task_model self.refreshButton = refresh From 93b9624181a1257f7de917a16e04736130eef9e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:21:05 +0200 Subject: [PATCH 096/110] fix key loaded from settings --- .../modules/ftrack/plugins/publish/collect_ftrack_family.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py b/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py index b505a429b5..8464a43ef7 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py +++ b/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py @@ -51,7 +51,7 @@ class CollectFtrackFamily(pyblish.api.InstancePlugin): families = instance.data.get("families") add_ftrack_family = profile["add_ftrack_family"] - additional_filters = profile.get("additional_filters") + additional_filters = profile.get("advanced_filtering") if additional_filters: add_ftrack_family = self._get_add_ftrack_f_from_addit_filters( additional_filters, From 83afef7392e09010019aa5a4029046b61189cdc2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 11:21:31 +0200 Subject: [PATCH 097/110] add ftrack family for review family --- openpype/settings/defaults/project_settings/ftrack.json | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 88f4e1e2e7..7f15742772 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -229,7 +229,6 @@ "standalonepublisher" ], "families": [ - "review", "plate" ], "tasks": [], From 622c6e6ca3a28e4da9251810581fd8155c9a23da Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Jul 2021 12:15:09 +0200 Subject: [PATCH 098/110] nuke: fixing wrong name of family folder when `used existing frames` --- openpype/hosts/nuke/plugins/publish/precollect_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index 00d96c6cd1..662f2c808e 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -81,18 +81,18 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): if target == "Use existing frames": # Local rendering self.log.info("flagged for no render") - families.append(family) + families.append(families_ak.lower()) elif target == "Local": # Local rendering self.log.info("flagged for local render") families.append("{}.local".format(family)) + family = families_ak.lower() elif target == "On farm": # Farm rendering self.log.info("flagged for farm render") instance.data["transfer"] = False families.append("{}.farm".format(family)) - - family = families_ak.lower() + family = families_ak.lower() node.begin() for i in nuke.allNodes(): From d00eec81d4de2e47b2ee6842ec92edc3bf9c2d78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 12:16:45 +0200 Subject: [PATCH 099/110] formatting keys fix --- openpype/hosts/houdini/plugins/publish/collect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_instances.py b/openpype/hosts/houdini/plugins/publish/collect_instances.py index 413553c864..2e294face2 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_instances.py +++ b/openpype/hosts/houdini/plugins/publish/collect_instances.py @@ -56,7 +56,7 @@ class CollectInstances(pyblish.api.ContextPlugin): # Create nice name if the instance has a frame range. label = data.get("name", node.name()) if "frameStart" in data and "frameEnd" in data: - frames = "[{startFrame} - {endFrame}]".format(**data) + frames = "[{frameStart} - {frameEnd}]".format(**data) label = "{} {}".format(label, frames) instance = context.create_instance(label) From 130e9ffa502605011f2ce75d351fa2f5280f7dfe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Jul 2021 12:33:09 +0200 Subject: [PATCH 100/110] nuke: fix review family switch --- openpype/hosts/nuke/plugins/publish/precollect_instances.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index 00d96c6cd1..9d671646c5 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -70,8 +70,9 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): review = False if "review" in node.knobs(): review = node["review"].value() + + if review: families.append("review") - families.append("ftrack") # Add all nodes in group instances. if node.Class() == "Group": From 97fe86cdd1faee28f67f66e418eea8ce7c7cfd6b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Jul 2021 12:37:11 +0200 Subject: [PATCH 101/110] settings: Ftrack family nuke preset --- .../defaults/project_settings/ftrack.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 88f4e1e2e7..78b34d5373 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -279,6 +279,25 @@ "tasks": [], "add_ftrack_family": true, "advanced_filtering": [] + }, + { + "hosts": [ + "nuke" + ], + "families": [ + "write", + "render" + ], + "tasks": [], + "add_ftrack_family": false, + "advanced_filtering": [ + { + "families": [ + "review" + ], + "add_ftrack_family": true + } + ] } ] }, From 54eb42f16ac024723270a3468fe72d03274b5b19 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 14:43:00 +0200 Subject: [PATCH 102/110] fix ignoring of missing defaults --- openpype/settings/entities/dict_conditional.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 641986491c..1ffc7ab450 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -384,16 +384,9 @@ class DictConditionalEntity(ItemEntity): # Set override state on enum entity first self.enum_entity.set_override_state(state, ignore_missing_defaults) - # Set override state on other entities under current enum value - for child_obj in self.non_gui_children[self.current_enum].values(): - child_obj.set_override_state(state, ignore_missing_defaults) - # Set override state on other enum children # - these must not raise exception about missing defaults for item_key, children_by_key in self.non_gui_children.items(): - if item_key == self.current_enum: - continue - for child_obj in children_by_key.values(): child_obj.set_override_state(state, True) From 696c72c34cc555a7af365c4f194a7f510b1151a7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 14:44:34 +0200 Subject: [PATCH 103/110] remove unusued variable --- openpype/settings/entities/dict_conditional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 1ffc7ab450..96065b670e 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -386,7 +386,7 @@ class DictConditionalEntity(ItemEntity): # Set override state on other enum children # - these must not raise exception about missing defaults - for item_key, children_by_key in self.non_gui_children.items(): + for children_by_key in self.non_gui_children.values(): for child_obj in children_by_key.values(): child_obj.set_override_state(state, True) From d60eeb85b4510efa7f6fc7207afa2be1e9c64e54 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Jul 2021 14:58:14 +0200 Subject: [PATCH 104/110] standalone: editorial plugins rename fix gap issue https://github.com/pypeclub/OpenPype/pull/1738#issuecomment-876373865 --- .../{collect_instances.py => collect_editorial_instances.py} | 5 ++++- ..._instance_resources.py => collect_editorial_resources.py} | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) rename openpype/hosts/standalonepublisher/plugins/publish/{collect_instances.py => collect_editorial_instances.py} (98%) rename openpype/hosts/standalonepublisher/plugins/publish/{collect_instance_resources.py => collect_editorial_resources.py} (99%) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py similarity index 98% rename from openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py index 0d95da444a..3474cbcdde 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py @@ -8,7 +8,7 @@ class CollectInstances(pyblish.api.InstancePlugin): """Collect instances from editorial's OTIO sequence""" order = pyblish.api.CollectorOrder + 0.01 - label = "Collect Instances" + label = "Collect Editorial Instances" hosts = ["standalonepublisher"] families = ["editorial"] @@ -84,6 +84,9 @@ class CollectInstances(pyblish.api.InstancePlugin): if clip.name is None: continue + if isinstance(clip, otio.schema.Gap): + continue + # skip all generators like black ampty if isinstance( clip.media_reference, diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_instance_resources.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_resources.py similarity index 99% rename from openpype/hosts/standalonepublisher/plugins/publish/collect_instance_resources.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_resources.py index 565d066fd8..e262009637 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_instance_resources.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_resources.py @@ -11,7 +11,7 @@ class CollectInstanceResources(pyblish.api.InstancePlugin): # must be after `CollectInstances` order = pyblish.api.CollectorOrder + 0.011 - label = "Collect Instance Resources" + label = "Collect Editorial Resources" hosts = ["standalonepublisher"] families = ["clip"] From f12c117f8669b381ba05dce11ec10c722c323630 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 9 Jul 2021 12:27:23 +0200 Subject: [PATCH 105/110] :window: dont add poetry to path, new installer --- tools/build.ps1 | 11 +++++------ tools/build_win_installer.ps1 | 8 -------- tools/create_env.ps1 | 11 ++++------- tools/create_zip.ps1 | 6 ++---- tools/fetch_thirdparty_libs.ps1 | 7 ++----- tools/make_docs.ps1 | 10 ++++------ tools/run_project_manager.ps1 | 4 ++-- tools/run_settings.ps1 | 4 ++-- tools/run_tests.ps1 | 6 ++---- tools/run_tray.ps1 | 4 ++-- 10 files changed, 25 insertions(+), 46 deletions(-) diff --git a/tools/build.ps1 b/tools/build.ps1 index 89795b0a50..cc4253fe24 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -83,7 +83,8 @@ function Show-PSWarning() { function Install-Poetry() { Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Installing Poetry ... " - (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - + $env:POETRY_HOME="$openpype_root\.poetry" + (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - } $art = @" @@ -115,11 +116,9 @@ $openpype_root = (Get-Item $script_dir).parent.FullName $env:_INSIDE_OPENPYPE_TOOL = "1" -# make sure Poetry is in PATH if (-not (Test-Path 'env:POETRY_HOME')) { $env:POETRY_HOME = "$openpype_root\.poetry" } -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" Set-Location -Path $openpype_root @@ -164,7 +163,7 @@ Write-Host " ]" -ForegroundColor white Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Write-Host "*** " -NoNewline -ForegroundColor Yellow Write-Host "We need to install Poetry create virtual env first ..." @@ -184,7 +183,7 @@ Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Building OpenPype ..." $startTime = [int][double]::Parse((Get-Date -UFormat %s)) -$out = & poetry run python setup.py build 2>&1 +$out = & "$($env:POETRY_HOME)\bin\poetry" run python setup.py build 2>&1 Set-Content -Path "$($openpype_root)\build\build.log" -Value $out if ($LASTEXITCODE -ne 0) { @@ -195,7 +194,7 @@ if ($LASTEXITCODE -ne 0) } Set-Content -Path "$($openpype_root)\build\build.log" -Value $out -& poetry run python "$($openpype_root)\tools\build_dependencies.py" +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\build_dependencies.py" Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "restoring current directory" diff --git a/tools/build_win_installer.ps1 b/tools/build_win_installer.ps1 index 05ec0f9823..a0832e0135 100644 --- a/tools/build_win_installer.ps1 +++ b/tools/build_win_installer.ps1 @@ -64,14 +64,6 @@ function Show-PSWarning() { } } -function Install-Poetry() { - Write-Host ">>> " -NoNewline -ForegroundColor Green - Write-Host "Installing Poetry ... " - (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - - # add it to PATH - $env:PATH = "$($env:PATH);$($env:USERPROFILE)\.poetry\bin" -} - $art = @" . . .. . .. diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index 94a91ce48f..6c8124ccb2 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -49,9 +49,7 @@ function Install-Poetry() { Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Installing Poetry ... " $env:POETRY_HOME="$openpype_root\.poetry" - (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - - # add it to PATH - $env:PATH = "$($env:PATH);$openpype_root\.poetry\bin" + (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python - } @@ -94,11 +92,10 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName -# make sure Poetry is in PATH if (-not (Test-Path 'env:POETRY_HOME')) { $env:POETRY_HOME = "$openpype_root\.poetry" } -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root @@ -145,7 +142,7 @@ Test-Python Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Install-Poetry Write-Host "INSTALLED" -ForegroundColor Cyan @@ -160,7 +157,7 @@ if (-not (Test-Path -PathType Leaf -Path "$($openpype_root)\poetry.lock")) { Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Installing virtual environment from lock." } -& poetry install --no-root $poetry_verbosity +& "$env:POETRY_HOME\bin\poetry" install --no-root $poetry_verbosity --ansi if ($LASTEXITCODE -ne 0) { Write-Host "!!! " -ForegroundColor yellow -NoNewline Write-Host "Poetry command failed." diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1 index 1a7520eb11..c27857b480 100644 --- a/tools/create_zip.ps1 +++ b/tools/create_zip.ps1 @@ -45,11 +45,9 @@ $openpype_root = (Get-Item $script_dir).parent.FullName $env:_INSIDE_OPENPYPE_TOOL = "1" -# make sure Poetry is in PATH if (-not (Test-Path 'env:POETRY_HOME')) { $env:POETRY_HOME = "$openpype_root\.poetry" } -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" Set-Location -Path $openpype_root @@ -87,7 +85,7 @@ if (-not $openpype_version) { Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Write-Host "*** " -NoNewline -ForegroundColor Yellow Write-Host "We need to install Poetry create virtual env first ..." @@ -107,5 +105,5 @@ Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Generating zip from current sources ..." $env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)" $env:OPENPYPE_ROOT="$($openpype_root)" -& poetry run python "$($openpype_root)\tools\create_zip.py" $ARGS +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\create_zip.py" $ARGS Set-Location -Path $current_dir diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index 23f0b50c7a..16f7b70e7a 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -17,18 +17,15 @@ $openpype_root = (Get-Item $script_dir).parent.FullName $env:_INSIDE_OPENPYPE_TOOL = "1" -# make sure Poetry is in PATH if (-not (Test-Path 'env:POETRY_HOME')) { $env:POETRY_HOME = "$openpype_root\.poetry" } -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" Set-Location -Path $openpype_root - Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Write-Host "*** " -NoNewline -ForegroundColor Yellow Write-Host "We need to install Poetry create virtual env first ..." @@ -37,5 +34,5 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { Write-Host "OK" -ForegroundColor Green } -& poetry run python "$($openpype_root)\tools\fetch_thirdparty_libs.py" +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\fetch_thirdparty_libs.py" Set-Location -Path $current_dir diff --git a/tools/make_docs.ps1 b/tools/make_docs.ps1 index 2f9350eff0..45a11171ae 100644 --- a/tools/make_docs.ps1 +++ b/tools/make_docs.ps1 @@ -19,11 +19,9 @@ $openpype_root = (Get-Item $script_dir).parent.FullName $env:_INSIDE_OPENPYPE_TOOL = "1" -# make sure Poetry is in PATH if (-not (Test-Path 'env:POETRY_HOME')) { $env:POETRY_HOME = "$openpype_root\.poetry" } -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" Set-Location -Path $openpype_root @@ -50,7 +48,7 @@ Write-Host $art -ForegroundColor DarkGreen Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Write-Host "*** " -NoNewline -ForegroundColor Yellow Write-Host "We need to install Poetry create virtual env first ..." @@ -63,10 +61,10 @@ Write-Host "This will not overwrite existing source rst files, only scan and add Set-Location -Path $openpype_root Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Running apidoc ..." -& poetry run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" igniter -& poetry run sphinx-apidoc.exe -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" openpype vendor, openpype\vendor +& "$env:POETRY_HOME\bin\poetry" run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" igniter +& "$env:POETRY_HOME\bin\poetry" run sphinx-apidoc.exe -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" openpype vendor, openpype\vendor Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Building html ..." -& poetry run python "$($openpype_root)\setup.py" build_sphinx +& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\setup.py" build_sphinx Set-Location -Path $current_dir diff --git a/tools/run_project_manager.ps1 b/tools/run_project_manager.ps1 index 9886a80316..a9cfbb1e7b 100644 --- a/tools/run_project_manager.ps1 +++ b/tools/run_project_manager.ps1 @@ -47,7 +47,7 @@ Set-Location -Path $openpype_root Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Write-Host "*** " -NoNewline -ForegroundColor Yellow Write-Host "We need to install Poetry create virtual env first ..." @@ -56,5 +56,5 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { Write-Host "OK" -ForegroundColor Green } -& poetry run python "$($openpype_root)\start.py" projectmanager +& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\start.py" projectmanager Set-Location -Path $current_dir diff --git a/tools/run_settings.ps1 b/tools/run_settings.ps1 index 7477e546b3..1c0aa6e8f3 100644 --- a/tools/run_settings.ps1 +++ b/tools/run_settings.ps1 @@ -27,7 +27,7 @@ Set-Location -Path $openpype_root Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Write-Host "*** " -NoNewline -ForegroundColor Yellow Write-Host "We need to install Poetry create virtual env first ..." @@ -36,5 +36,5 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { Write-Host "OK" -ForegroundColor Green } -& poetry run python "$($openpype_root)\start.py" settings --dev +& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\start.py" settings --dev Set-Location -Path $current_dir \ No newline at end of file diff --git a/tools/run_tests.ps1 b/tools/run_tests.ps1 index a6882e2a09..e631cb72df 100644 --- a/tools/run_tests.ps1 +++ b/tools/run_tests.ps1 @@ -59,11 +59,9 @@ $openpype_root = (Get-Item $script_dir).parent.FullName $env:_INSIDE_OPENPYPE_TOOL = "1" -# make sure Poetry is in PATH if (-not (Test-Path 'env:POETRY_HOME')) { $env:POETRY_HOME = "$openpype_root\.poetry" } -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" Set-Location -Path $openpype_root @@ -83,7 +81,7 @@ Write-Host " ] ..." -ForegroundColor white Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Write-Host "*** " -NoNewline -ForegroundColor Yellow Write-Host "We need to install Poetry create virtual env first ..." @@ -102,7 +100,7 @@ Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Testing OpenPype ..." $original_pythonpath = $env:PYTHONPATH $env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)" -& poetry run pytest -x --capture=sys --print -W ignore::DeprecationWarning "$($openpype_root)/tests" +& "$env:POETRY_HOME\bin\poetry" run pytest -x --capture=sys --print -W ignore::DeprecationWarning "$($openpype_root)/tests" $env:PYTHONPATH = $original_pythonpath Write-Host ">>> " -NoNewline -ForegroundColor green diff --git a/tools/run_tray.ps1 b/tools/run_tray.ps1 index 533a791836..872c1524a6 100644 --- a/tools/run_tray.ps1 +++ b/tools/run_tray.ps1 @@ -26,7 +26,7 @@ Set-Location -Path $openpype_root Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Write-Host "*** " -NoNewline -ForegroundColor Yellow Write-Host "We need to install Poetry create virtual env first ..." @@ -35,5 +35,5 @@ if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { Write-Host "OK" -ForegroundColor Green } -& poetry run python "$($openpype_root)\start.py" tray --debug +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" tray --debug Set-Location -Path $current_dir \ No newline at end of file From 43f7f8276aa714a465e9970a8cd8430b38772691 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 9 Jul 2021 12:38:19 +0200 Subject: [PATCH 106/110] =?UTF-8?q?=F0=9F=90=A7=F0=9F=8D=8E=20don't=20add?= =?UTF-8?q?=20poetry=20to=20path,=20new=20installer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/build.sh | 23 +++-------------------- tools/create_env.sh | 16 +++++++--------- tools/create_zip.sh | 4 +--- tools/fetch_thirdparty_libs.sh | 7 +------ tools/make_docs.sh | 8 +++----- tools/run_projectmanager.sh | 4 +--- tools/run_settings.sh | 4 +--- tools/run_tests.sh | 4 +--- tools/run_tray.sh | 4 +--- 9 files changed, 19 insertions(+), 55 deletions(-) diff --git a/tools/build.sh b/tools/build.sh index aa8f0121ea..c44e7157af 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -140,21 +140,6 @@ realpath () { echo $(cd $(dirname "$1") || return; pwd)/$(basename "$1") } -############################################################################## -# Install Poetry when needed -# Globals: -# PATH -# Arguments: -# None -# Returns: -# None -############################################################################### -install_poetry () { - echo -e "${BIGreen}>>>${RST} Installing Poetry ..." - command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - -} - # Main main () { echo -e "${BGreen}" @@ -171,11 +156,9 @@ main () { _inside_openpype_tool="1" - # make sure Poetry is in PATH if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" echo -e "${BIYellow}---${RST} Cleaning build directory ..." rm -rf "$openpype_root/build" && mkdir "$openpype_root/build" > /dev/null @@ -201,11 +184,11 @@ if [ "$disable_submodule_update" == 1 ]; then fi echo -e "${BIGreen}>>>${RST} Building ..." if [[ "$OSTYPE" == "linux-gnu"* ]]; then - poetry run python "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + "$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } elif [[ "$OSTYPE" == "darwin"* ]]; then - poetry run python "$openpype_root/setup.py" bdist_mac > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } + "$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" bdist_mac > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; } fi - poetry run python "$openpype_root/tools/build_dependencies.py" + "$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/build_dependencies.py" if [[ "$OSTYPE" == "darwin"* ]]; then # fix code signing issue diff --git a/tools/create_env.sh b/tools/create_env.sh index 226a26e199..cc9eddc317 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -109,8 +109,7 @@ install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." export POETRY_HOME="$openpype_root/.poetry" command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - - export PATH="$PATH:$POETRY_HOME/bin" + curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - } ############################################################################## @@ -154,11 +153,10 @@ main () { # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) - # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" pushd "$openpype_root" > /dev/null || return > /dev/null @@ -177,7 +175,7 @@ main () { echo -e "${BIGreen}>>>${RST} Installing dependencies ..." fi - poetry install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return; } + "$POETRY_HOME/bin/poetry" install --no-root $poetry_verbosity || { echo -e "${BIRed}!!!${RST} Poetry environment installation failed"; return; } echo -e "${BIGreen}>>>${RST} Cleaning cache files ..." clean_pyc @@ -186,10 +184,10 @@ main () { # cx_freeze will crash on missing __pychache__ on these but # reinstalling them solves the problem. echo -e "${BIGreen}>>>${RST} Fixing pycache bug ..." - poetry run python -m pip install --force-reinstall pip - poetry run pip install --force-reinstall setuptools - poetry run pip install --force-reinstall wheel - poetry run python -m pip install --force-reinstall pip + "$POETRY_HOME/bin/poetry" run python -m pip install --force-reinstall pip + "$POETRY_HOME/bin/poetry" run pip install --force-reinstall setuptools + "$POETRY_HOME/bin/poetry" run pip install --force-reinstall wheel + "$POETRY_HOME/bin/poetry" run python -m pip install --force-reinstall pip } main -3 diff --git a/tools/create_zip.sh b/tools/create_zip.sh index ec0276b040..85ee18a839 100755 --- a/tools/create_zip.sh +++ b/tools/create_zip.sh @@ -114,11 +114,9 @@ main () { _inside_openpype_tool="1" - # make sure Poetry is in PATH if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" pushd "$openpype_root" > /dev/null || return > /dev/null @@ -134,7 +132,7 @@ main () { echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..." PYTHONPATH="$openpype_root:$PYTHONPATH" OPENPYPE_ROOT="$openpype_root" - poetry run python3 "$openpype_root/tools/create_zip.py" "$@" + "$POETRY_HOME/bin/poetry" run python3 "$openpype_root/tools/create_zip.py" "$@" } main "$@" diff --git a/tools/fetch_thirdparty_libs.sh b/tools/fetch_thirdparty_libs.sh index 31f109ba68..93d0674965 100755 --- a/tools/fetch_thirdparty_libs.sh +++ b/tools/fetch_thirdparty_libs.sh @@ -1,8 +1,5 @@ #!/usr/bin/env bash -# Run Pype Tray - - art () { cat <<-EOF @@ -82,11 +79,9 @@ main () { _inside_openpype_tool="1" - # make sure Poetry is in PATH if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" if [ -f "$POETRY_HOME/bin/poetry" ]; then @@ -100,7 +95,7 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Running Pype tool ..." - poetry run python "$openpype_root/tools/fetch_thirdparty_libs.py" + "$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/fetch_thirdparty_libs.py" } main \ No newline at end of file diff --git a/tools/make_docs.sh b/tools/make_docs.sh index 9dfab26a38..52ee57dcf0 100755 --- a/tools/make_docs.sh +++ b/tools/make_docs.sh @@ -83,11 +83,9 @@ main () { _inside_openpype_tool="1" - # make sure Poetry is in PATH if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" if [ -f "$POETRY_HOME/bin/poetry" ]; then @@ -101,11 +99,11 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Running apidoc ..." - poetry run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$openpype_root/docs/source" igniter - poetry run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$openpype_root/docs/source" openpype vendor, openpype\vendor + "$POETRY_HOME/bin/poetry" run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$openpype_root/docs/source" igniter + "$POETRY_HOME/bin/poetry" run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$openpype_root/docs/source" openpype vendor, openpype\vendor echo -e "${BIGreen}>>>${RST} Building html ..." - poetry run python3 "$openpype_root/setup.py" build_sphinx + "$POETRY_HOME/bin/poetry" run python3 "$openpype_root/setup.py" build_sphinx } main diff --git a/tools/run_projectmanager.sh b/tools/run_projectmanager.sh index 312f321d67..b5c858c34a 100755 --- a/tools/run_projectmanager.sh +++ b/tools/run_projectmanager.sh @@ -79,11 +79,9 @@ main () { _inside_openpype_tool="1" - # make sure Poetry is in PATH if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" pushd "$openpype_root" > /dev/null || return > /dev/null @@ -97,7 +95,7 @@ main () { fi echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..." - poetry run python "$openpype_root/start.py" projectmanager + "$POETRY_HOME/bin/poetry" run python "$openpype_root/start.py" projectmanager } main diff --git a/tools/run_settings.sh b/tools/run_settings.sh index 0287043bb6..5a465dce2c 100755 --- a/tools/run_settings.sh +++ b/tools/run_settings.sh @@ -79,11 +79,9 @@ main () { _inside_openpype_tool="1" - # make sure Poetry is in PATH if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" pushd "$openpype_root" > /dev/null || return > /dev/null @@ -97,7 +95,7 @@ main () { fi echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..." - poetry run python3 "$openpype_root/start.py" settings --dev + "$POETRY_HOME/bin/poetry" run python3 "$openpype_root/start.py" settings --dev } main diff --git a/tools/run_tests.sh b/tools/run_tests.sh index 90977edc83..8f8f82fd9c 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -98,11 +98,9 @@ main () { _inside_openpype_tool="1" - # make sure Poetry is in PATH if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" if [ -f "$POETRY_HOME/bin/poetry" ]; then @@ -118,7 +116,7 @@ main () { echo -e "${BIGreen}>>>${RST} Testing OpenPype ..." original_pythonpath=$PYTHONPATH export PYTHONPATH="$openpype_root:$PYTHONPATH" - poetry run pytest -x --capture=sys --print -W ignore::DeprecationWarning "$openpype_root/tests" + "$POETRY_HOME/bin/poetry" run pytest -x --capture=sys --print -W ignore::DeprecationWarning "$openpype_root/tests" PYTHONPATH=$original_pythonpath } diff --git a/tools/run_tray.sh b/tools/run_tray.sh index 339ff6f918..2eb9886063 100755 --- a/tools/run_tray.sh +++ b/tools/run_tray.sh @@ -56,11 +56,9 @@ main () { _inside_openpype_tool="1" - # make sure Poetry is in PATH if [[ -z $POETRY_HOME ]]; then export POETRY_HOME="$openpype_root/.poetry" fi - export PATH="$POETRY_HOME/bin:$PATH" echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" if [ -f "$POETRY_HOME/bin/poetry" ]; then @@ -74,7 +72,7 @@ main () { pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Running OpenPype Tray with debug option ..." - poetry run python3 "$openpype_root/start.py" tray --debug + "$POETRY_HOME/bin/poetry" run python3 "$openpype_root/start.py" tray --debug } main \ No newline at end of file From 39efe7e105b9d6d41cb9e16902086a7f97d09838 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 10 Jul 2021 03:41:06 +0000 Subject: [PATCH 107/110] [Automated] Bump version --- CHANGELOG.md | 57 +++++++++++++++------------------------------ openpype/version.py | 2 +- 2 files changed, 20 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e658d6995..5e76d7b76a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,16 @@ # Changelog -## [3.2.0-nightly.6](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.2.0-nightly.7](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/2.18.4...HEAD) **🚀 Enhancements** +- Nuke: ftrack family plugin settings preset [\#1805](https://github.com/pypeclub/OpenPype/pull/1805) +- Standalone publisher last project [\#1799](https://github.com/pypeclub/OpenPype/pull/1799) +- Ftrack Multiple notes as server action [\#1795](https://github.com/pypeclub/OpenPype/pull/1795) +- Settings conditional dict [\#1777](https://github.com/pypeclub/OpenPype/pull/1777) - Settings application use python 2 only where needed [\#1776](https://github.com/pypeclub/OpenPype/pull/1776) -- Settings UI copy/paste [\#1769](https://github.com/pypeclub/OpenPype/pull/1769) - Workfile tool widths [\#1766](https://github.com/pypeclub/OpenPype/pull/1766) - Push hierarchical attributes care about task parent changes [\#1763](https://github.com/pypeclub/OpenPype/pull/1763) - Application executables with environment variables [\#1757](https://github.com/pypeclub/OpenPype/pull/1757) @@ -16,18 +19,18 @@ - Settings Hosts enum [\#1739](https://github.com/pypeclub/OpenPype/pull/1739) - Validate containers settings [\#1736](https://github.com/pypeclub/OpenPype/pull/1736) - PS - added loader from sequence [\#1726](https://github.com/pypeclub/OpenPype/pull/1726) -- Autoupdate launcher [\#1725](https://github.com/pypeclub/OpenPype/pull/1725) -- Subset template and TVPaint subset template docs [\#1717](https://github.com/pypeclub/OpenPype/pull/1717) - Toggle Ftrack upload in StandalonePublisher [\#1708](https://github.com/pypeclub/OpenPype/pull/1708) -- Overscan color extract review [\#1701](https://github.com/pypeclub/OpenPype/pull/1701) -- Nuke: Prerender Frame Range by default [\#1699](https://github.com/pypeclub/OpenPype/pull/1699) -- Smoother edges of color triangle [\#1695](https://github.com/pypeclub/OpenPype/pull/1695) **🐛 Bug fixes** +- nuke: fixing wrong name of family folder when `used existing frames` [\#1803](https://github.com/pypeclub/OpenPype/pull/1803) +- Collect ftrack family bugs [\#1801](https://github.com/pypeclub/OpenPype/pull/1801) +- Invitee email can be None which break the Ftrack commit. [\#1788](https://github.com/pypeclub/OpenPype/pull/1788) +- Fix: staging and `--use-version` option [\#1786](https://github.com/pypeclub/OpenPype/pull/1786) - Otio unrelated error on import [\#1782](https://github.com/pypeclub/OpenPype/pull/1782) - FFprobe streams order [\#1775](https://github.com/pypeclub/OpenPype/pull/1775) - Fix - single file files are str only, cast it to list to count properly [\#1772](https://github.com/pypeclub/OpenPype/pull/1772) +- Environments in app executable for MacOS [\#1768](https://github.com/pypeclub/OpenPype/pull/1768) - Project specific environments [\#1767](https://github.com/pypeclub/OpenPype/pull/1767) - Settings UI with refresh button [\#1764](https://github.com/pypeclub/OpenPype/pull/1764) - Standalone publisher thumbnail extractor fix [\#1761](https://github.com/pypeclub/OpenPype/pull/1761) @@ -36,22 +39,18 @@ - hiero: precollect instances failing when audio selected [\#1743](https://github.com/pypeclub/OpenPype/pull/1743) - Hiero: creator instance error [\#1742](https://github.com/pypeclub/OpenPype/pull/1742) - Nuke: fixing render creator for no selection format failing [\#1741](https://github.com/pypeclub/OpenPype/pull/1741) +- StandalonePublisher: failing collector for editorial [\#1738](https://github.com/pypeclub/OpenPype/pull/1738) - Local settings UI crash on missing defaults [\#1737](https://github.com/pypeclub/OpenPype/pull/1737) - TVPaint white background on thumbnail [\#1735](https://github.com/pypeclub/OpenPype/pull/1735) -- Ftrack missing custom attribute message [\#1734](https://github.com/pypeclub/OpenPype/pull/1734) -- Launcher project changes [\#1733](https://github.com/pypeclub/OpenPype/pull/1733) -- Ftrack sync status [\#1732](https://github.com/pypeclub/OpenPype/pull/1732) -- TVPaint use layer name for default variant [\#1724](https://github.com/pypeclub/OpenPype/pull/1724) -- Default subset template for TVPaint review and workfile families [\#1716](https://github.com/pypeclub/OpenPype/pull/1716) -- Maya: Extract review hotfix [\#1714](https://github.com/pypeclub/OpenPype/pull/1714) -- Settings: Imageio improving granularity [\#1711](https://github.com/pypeclub/OpenPype/pull/1711) - Application without executables [\#1679](https://github.com/pypeclub/OpenPype/pull/1679) +- Unreal: launching on Linux [\#1672](https://github.com/pypeclub/OpenPype/pull/1672) **Merged pull requests:** +- Build: don't add Poetry to `PATH` [\#1808](https://github.com/pypeclub/OpenPype/pull/1808) - Bump prismjs from 1.23.0 to 1.24.0 in /website [\#1773](https://github.com/pypeclub/OpenPype/pull/1773) +- Bc/fix/docs [\#1771](https://github.com/pypeclub/OpenPype/pull/1771) - TVPaint ftrack family [\#1755](https://github.com/pypeclub/OpenPype/pull/1755) -- Sync main 2.x back to 2.x develop [\#1715](https://github.com/pypeclub/OpenPype/pull/1715) ## [2.18.4](https://github.com/pypeclub/OpenPype/tree/2.18.4) (2021-06-24) @@ -66,26 +65,21 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.2.0-nightly.2...2.18.3) -**🚀 Enhancements** - -- Log Viewer with OpenPype style [\#1703](https://github.com/pypeclub/OpenPype/pull/1703) - **🐛 Bug fixes** - Tools names forwards compatibility [\#1727](https://github.com/pypeclub/OpenPype/pull/1727) +**Merged pull requests:** + +- global: removing obsolete ftrack validator plugin [\#1710](https://github.com/pypeclub/OpenPype/pull/1710) + ## [2.18.2](https://github.com/pypeclub/OpenPype/tree/2.18.2) (2021-06-16) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.1.0...2.18.2) -**🚀 Enhancements** - -- StandalonePublisher: adding exception for adding `delete` tag to repre [\#1650](https://github.com/pypeclub/OpenPype/pull/1650) - **🐛 Bug fixes** - Maya: Extract review hotfix - 2.x backport [\#1713](https://github.com/pypeclub/OpenPype/pull/1713) -- StandalonePublisher: instance data attribute `keepSequence` [\#1668](https://github.com/pypeclub/OpenPype/pull/1668) **Merged pull requests:** @@ -97,19 +91,12 @@ **🚀 Enhancements** +- Log Viewer with OpenPype style [\#1703](https://github.com/pypeclub/OpenPype/pull/1703) - Scrolling in OpenPype info widget [\#1702](https://github.com/pypeclub/OpenPype/pull/1702) - OpenPype style in modules [\#1694](https://github.com/pypeclub/OpenPype/pull/1694) - Sort applications and tools alphabetically in Settings UI [\#1689](https://github.com/pypeclub/OpenPype/pull/1689) - \#683 - Validate Frame Range in Standalone Publisher [\#1683](https://github.com/pypeclub/OpenPype/pull/1683) - Hiero: old container versions identify with red color [\#1682](https://github.com/pypeclub/OpenPype/pull/1682) -- Project Manger: Default name column width [\#1669](https://github.com/pypeclub/OpenPype/pull/1669) -- Remove outline in stylesheet [\#1667](https://github.com/pypeclub/OpenPype/pull/1667) -- TVPaint: Creator take layer name as default value for subset variant [\#1663](https://github.com/pypeclub/OpenPype/pull/1663) -- TVPaint custom subset template [\#1662](https://github.com/pypeclub/OpenPype/pull/1662) -- Editorial: conform assets validator [\#1659](https://github.com/pypeclub/OpenPype/pull/1659) -- Feature Slack integration [\#1657](https://github.com/pypeclub/OpenPype/pull/1657) -- Nuke - Publish simplification [\#1653](https://github.com/pypeclub/OpenPype/pull/1653) -- \#1333 - added tooltip hints to Pyblish buttons [\#1649](https://github.com/pypeclub/OpenPype/pull/1649) **🐛 Bug fixes** @@ -119,15 +106,9 @@ - Hiero: published whole edit mov [\#1687](https://github.com/pypeclub/OpenPype/pull/1687) - Ftrack subprocess handle of stdout/stderr [\#1675](https://github.com/pypeclub/OpenPype/pull/1675) - Settings list race condifiton and mutable dict list conversion [\#1671](https://github.com/pypeclub/OpenPype/pull/1671) -- Mac launch arguments fix [\#1660](https://github.com/pypeclub/OpenPype/pull/1660) -- Fix missing dbm python module [\#1652](https://github.com/pypeclub/OpenPype/pull/1652) -- Transparent branches in view on Mac [\#1648](https://github.com/pypeclub/OpenPype/pull/1648) -- Add asset on task item [\#1646](https://github.com/pypeclub/OpenPype/pull/1646) -- Project manager save and queue [\#1645](https://github.com/pypeclub/OpenPype/pull/1645) **Merged pull requests:** -- global: removing obsolete ftrack validator plugin [\#1710](https://github.com/pypeclub/OpenPype/pull/1710) - update dependencies [\#1697](https://github.com/pypeclub/OpenPype/pull/1697) - Bump normalize-url from 4.5.0 to 4.5.1 in /website [\#1686](https://github.com/pypeclub/OpenPype/pull/1686) diff --git a/openpype/version.py b/openpype/version.py index 86d62a83d0..dabeacc084 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.2.0-nightly.6" +__version__ = "3.2.0-nightly.7" From 4b2aba2f450dfc1a9a46142d8b6df441ac8bd2f6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 12 Jul 2021 12:59:19 +0200 Subject: [PATCH 108/110] change settings retrieval --- .../plugins/publish/submit_maya_deadline.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 0e09641200..c0c39a52b6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -272,18 +272,19 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): ) self._job_info = ( - context.data["project_settings"] - ["maya"] - ["publish"] - ["deadline"] - ["jobInfo"] + context.data["project_settings"].get( + "maya", {}).get( + "publish", {}).get( + "deadline", {}).get( + "jobInfo", {}) ) + self._plugin_info = ( - context.data["project_settings"] - ["maya"] - ["publish"] - ["deadline"] - ["pluginInfo"] + context.data["project_settings"].get( + "maya", {}).get( + "publish", {}).get( + "deadline", {}).get( + "pluginInfo", {}) ) assert self._deadline_url, "Requires DEADLINE_REST_URL" From 0551d76fb6ad74e38de35ada406e41c1acb614c7 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 12 Jul 2021 13:16:38 +0200 Subject: [PATCH 109/110] move settings to deadline --- .../plugins/publish/submit_maya_deadline.py | 8 ++++---- .../defaults/project_settings/deadline.json | 14 ++++++++++---- .../defaults/project_settings/maya.json | 4 ---- .../schema_project_deadline.json | 10 ++++++++++ .../schemas/schema_maya_publish.json | 18 ------------------ 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index c0c39a52b6..5cb6dbbd88 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -273,17 +273,17 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): self._job_info = ( context.data["project_settings"].get( - "maya", {}).get( - "publish", {}).get( "deadline", {}).get( + "publish", {}).get( + "MayaSubmitDeadline", {}).get( "jobInfo", {}) ) self._plugin_info = ( context.data["project_settings"].get( - "maya", {}).get( - "publish", {}).get( "deadline", {}).get( + "publish", {}).get( + "MayaSubmitDeadline", {}).get( "pluginInfo", {}) ) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5861015f2c..2dba20d63c 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -3,9 +3,13 @@ "ValidateExpectedFiles": { "enabled": true, "active": true, - "families": ["render"], - "targets": ["deadline"], - "allow_user_override": true + "allow_user_override": true, + "families": [ + "render" + ], + "targets": [ + "deadline" + ] }, "MayaSubmitDeadline": { "enabled": true, @@ -15,7 +19,9 @@ "use_published": true, "asset_dependencies": true, "group": "none", - "limit": [] + "limit": [], + "jobInfo": {}, + "pluginInfo": {} }, "NukeSubmitDeadline": { "enabled": true, diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 62d8a74670..284a1a0040 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -391,10 +391,6 @@ } } }, - "deadline": { - "jobInfo": {}, - "pluginInfo": {} - }, "ExtractCameraAlembic": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 3281c9ce4d..27eeaef559 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -108,6 +108,16 @@ "key": "limit", "label": "Limit Groups", "object_type": "text" + }, + { + "type": "raw-json", + "key": "jobInfo", + "label": "Additional JobInfo data" + }, + { + "type": "raw-json", + "key": "pluginInfo", + "label": "Additional PluginInfo data" } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 7e50682f5d..5ca7059ee5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -309,24 +309,6 @@ "type": "schema", "name": "schema_maya_capture" }, - { - "type": "dict", - "collapsible": true, - "key": "deadline", - "label": "Additional Deadline Settings", - "children": [ - { - "type": "raw-json", - "key": "jobInfo", - "label": "Additional JobInfo data" - }, - { - "type": "raw-json", - "key": "pluginInfo", - "label": "Additional PluginInfo data" - } - ] - }, { "type": "dict", "collapsible": true, From 4d96cdc7200808c6accbb190e4ffea3f59a74cd6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 12 Jul 2021 13:34:19 +0200 Subject: [PATCH 110/110] fix check for Group defaults --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 5cb6dbbd88..a652da7786 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -423,7 +423,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): self.payload_skeleton["JobInfo"]["Priority"] = \ self._instance.data.get("priority", 50) - if self.group != "none": + if self.group != "none" and self.group: self.payload_skeleton["JobInfo"]["Group"] = self.group if self.limit_groups: