From e8d6f1d9e9764f90128938ce1386a5b767607c60 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 22:02:56 +0100 Subject: [PATCH 001/513] Pull Fusion integration from `colorbleed` --- openpype/hosts/fusion/__init__.py | 5 + openpype/hosts/fusion/api/menu.py | 57 ++++++--- openpype/hosts/fusion/api/menu_style.qss | 29 ----- .../fusion/deploy/Config/openpype_menu.fu | 60 +++++++++ .../hosts/fusion/deploy/MenuScripts/README.md | 6 + .../deploy/MenuScripts/install_pyside2.py | 29 +++++ .../MenuScripts/openpype_menu.py} | 10 ++ .../32bit/backgrounds_selected_to32bit.py | 0 .../Comp}/32bit/backgrounds_to32bit.py | 0 .../Comp}/32bit/loaders_selected_to32bit.py | 0 .../Scripts/Comp}/32bit/loaders_to32bit.py | 0 .../Scripts/Comp}/switch_ui.py | 0 .../Scripts/Comp}/update_loader_ranges.py | 0 .../hosts/fusion/deploy/fusion_shared.prefs | 19 +++ .../hosts/fusion/hooks/pre_fusion_setup.py | 114 ++++-------------- openpype/hosts/fusion/plugins/load/actions.py | 2 + .../fusion/scripts/fusion_switch_shot.py | 2 +- 17 files changed, 197 insertions(+), 136 deletions(-) delete mode 100644 openpype/hosts/fusion/api/menu_style.qss create mode 100644 openpype/hosts/fusion/deploy/Config/openpype_menu.fu create mode 100644 openpype/hosts/fusion/deploy/MenuScripts/README.md create mode 100644 openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py rename openpype/hosts/fusion/{utility_scripts/__OpenPype_Menu__.py => deploy/MenuScripts/openpype_menu.py} (54%) rename openpype/hosts/fusion/{utility_scripts => deploy/Scripts/Comp}/32bit/backgrounds_selected_to32bit.py (100%) rename openpype/hosts/fusion/{utility_scripts => deploy/Scripts/Comp}/32bit/backgrounds_to32bit.py (100%) rename openpype/hosts/fusion/{utility_scripts => deploy/Scripts/Comp}/32bit/loaders_selected_to32bit.py (100%) rename openpype/hosts/fusion/{utility_scripts => deploy/Scripts/Comp}/32bit/loaders_to32bit.py (100%) rename openpype/hosts/fusion/{utility_scripts => deploy/Scripts/Comp}/switch_ui.py (100%) rename openpype/hosts/fusion/{utility_scripts => deploy/Scripts/Comp}/update_loader_ranges.py (100%) create mode 100644 openpype/hosts/fusion/deploy/fusion_shared.prefs diff --git a/openpype/hosts/fusion/__init__.py b/openpype/hosts/fusion/__init__.py index e69de29bb2..02befa76e2 100644 --- a/openpype/hosts/fusion/__init__.py +++ b/openpype/hosts/fusion/__init__.py @@ -0,0 +1,5 @@ +import os + +HOST_DIR = os.path.dirname( + os.path.abspath(__file__) +) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 6234322d7f..7799528462 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -3,26 +3,16 @@ import sys from Qt import QtWidgets, QtCore -from openpype import style +from avalon import api from openpype.tools.utils import host_tools +from openpype.style import load_stylesheet from openpype.hosts.fusion.scripts import ( set_rendermode, duplicate_with_inputs ) -def load_stylesheet(): - path = os.path.join(os.path.dirname(__file__), "menu_style.qss") - if not os.path.exists(path): - print("Unable to load stylesheet, file not found in resources") - return "" - - with open(path, "r") as file_stream: - stylesheet = file_stream.read() - return stylesheet - - class Spacer(QtWidgets.QWidget): def __init__(self, height, *args, **kwargs): super(Spacer, self).__init__(*args, **kwargs) @@ -55,6 +45,15 @@ class OpenPypeMenu(QtWidgets.QWidget): ) self.render_mode_widget = None self.setWindowTitle("OpenPype") + + asset_label = QtWidgets.QLabel("Context", self) + asset_label.setStyleSheet("""QLabel { + font-size: 14px; + font-weight: 600; + color: #5f9fb8; + }""") + asset_label.setAlignment(QtCore.Qt.AlignHCenter) + workfiles_btn = QtWidgets.QPushButton("Workfiles...", self) create_btn = QtWidgets.QPushButton("Create...", self) publish_btn = QtWidgets.QPushButton("Publish...", self) @@ -72,10 +71,17 @@ class OpenPypeMenu(QtWidgets.QWidget): layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(10, 20, 10, 20) + layout.addWidget(asset_label) + + layout.addWidget(Spacer(15, self)) + layout.addWidget(workfiles_btn) + + layout.addWidget(Spacer(15, self)) + layout.addWidget(create_btn) - layout.addWidget(publish_btn) layout.addWidget(load_btn) + layout.addWidget(publish_btn) layout.addWidget(manager_btn) layout.addWidget(Spacer(15, self)) @@ -93,6 +99,9 @@ class OpenPypeMenu(QtWidgets.QWidget): self.setLayout(layout) + # Store reference so we can update the label + self.asset_label = asset_label + workfiles_btn.clicked.connect(self.on_workfile_clicked) create_btn.clicked.connect(self.on_create_clicked) publish_btn.clicked.connect(self.on_publish_clicked) @@ -104,6 +113,26 @@ class OpenPypeMenu(QtWidgets.QWidget): self.on_duplicate_with_inputs_clicked) reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) + self._callbacks = [] + self.register_callback("taskChanged", self.on_task_changed) + self.on_task_changed() + + def on_task_changed(self): + # Update current context label + label = api.Session["AVALON_ASSET"] + self.asset_label.setText(label) + + def register_callback(self, name, fn): + + # Create a wrapper callback that we only store + # for as long as we want it to persist as callback + callback = lambda *args: fn() + self._callbacks.append(callback) + api.on(name, callback) + + def deregister_all_callbacks(self): + self._callbacks[:] = [] + def on_workfile_clicked(self): print("Clicked Workfile") host_tools.show_workfiles() @@ -132,7 +161,7 @@ class OpenPypeMenu(QtWidgets.QWidget): print("Clicked Set Render Mode") if self.render_mode_widget is None: window = set_rendermode.SetRenderMode() - window.setStyleSheet(style.load_stylesheet()) + window.setStyleSheet(load_stylesheet()) window.show() self.render_mode_widget = window else: diff --git a/openpype/hosts/fusion/api/menu_style.qss b/openpype/hosts/fusion/api/menu_style.qss deleted file mode 100644 index 12c474b070..0000000000 --- a/openpype/hosts/fusion/api/menu_style.qss +++ /dev/null @@ -1,29 +0,0 @@ -QWidget { - background-color: #282828; - border-radius: 3; -} - -QPushButton { - border: 1px solid #090909; - background-color: #201f1f; - color: #ffffff; - padding: 5; -} - -QPushButton:focus { - background-color: "#171717"; - color: #d0d0d0; -} - -QPushButton:hover { - background-color: "#171717"; - color: #e64b3d; -} - -#OpenPypeMenu { - border: 1px solid #fef9ef; -} - -#Spacer { - background-color: #282828; -} diff --git a/openpype/hosts/fusion/deploy/Config/openpype_menu.fu b/openpype/hosts/fusion/deploy/Config/openpype_menu.fu new file mode 100644 index 0000000000..8b8d448259 --- /dev/null +++ b/openpype/hosts/fusion/deploy/Config/openpype_menu.fu @@ -0,0 +1,60 @@ +{ + Action + { + ID = "OpenPype_Menu", + Category = "OpenPype", + Name = "OpenPype Menu", + + Targets = + { + Composition = + { + Execute = _Lua [=[ + local scriptPath = app:MapPath("OpenPype:MenuScripts/openpype_menu.py") + if bmd.fileexists(scriptPath) == false then + print("[OpenPype Error] Can't run file: " .. scriptPath) + else + target:RunScript(scriptPath) + end + ]=], + }, + }, + }, + Action + { + ID = "OpenPype_Install_PySide2", + Category = "OpenPype", + Name = "Install PySide2", + + Targets = + { + Composition = + { + Execute = _Lua [=[ + local scriptPath = app:MapPath("OpenPype:MenuScripts/install_pyside2.py") + if bmd.fileexists(scriptPath) == false then + print("[OpenPype Error] Can't run file: " .. scriptPath) + else + target:RunScript(scriptPath) + end + ]=], + }, + }, + }, + Menus + { + Target = "ChildFrame", + + Before "Help" + { + Sub "OpenPype" + { + "OpenPype_Menu{}", + "_", + Sub "Admin" { + "OpenPype_Install_PySide2{}" + } + } + }, + }, +} diff --git a/openpype/hosts/fusion/deploy/MenuScripts/README.md b/openpype/hosts/fusion/deploy/MenuScripts/README.md new file mode 100644 index 0000000000..f87eaea4a2 --- /dev/null +++ b/openpype/hosts/fusion/deploy/MenuScripts/README.md @@ -0,0 +1,6 @@ +### OpenPype deploy MenuScripts + +Note that this `MenuScripts` is not an official Fusion folder. +OpenPype only uses this folder in `{fusion}/deploy/` to trigger the OpenPype menu actions. + +They are used in the actions defined in `.fu` files in `{fusion}/deploy/Config`. \ No newline at end of file diff --git a/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py b/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py new file mode 100644 index 0000000000..4fcdb7658f --- /dev/null +++ b/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py @@ -0,0 +1,29 @@ +# This is just a quick hack for users running Py3 locally but having no +# Qt library installed +import os +import subprocess +import importlib + + +try: + from Qt import QtWidgets + from Qt import __binding__ + print(f"Qt binding: {__binding__}") + mod = importlib.import_module(__binding__) + print(f"Qt path: {mod.__file__}") + print("Qt library found, nothing to do..") + +except ImportError as exc: + print("Assuming no Qt library is installed..") + print('Installing PySide2 for Python 3.6: ' + f'{os.environ["FUSION16_PYTHON36_HOME"]}') + + # Get full path to python executable + exe = "python.exe" if os.name == 'nt' else "python" + python = os.path.join(os.environ["FUSION16_PYTHON36_HOME"], exe) + assert os.path.exists(python), f"Python doesn't exist: {python}" + + # Do python -m pip install PySide2 + args = [python, "-m", "pip", "install", "PySide2"] + print(f"Args: {args}") + subprocess.Popen(args) diff --git a/openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py b/openpype/hosts/fusion/deploy/MenuScripts/openpype_menu.py similarity index 54% rename from openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py rename to openpype/hosts/fusion/deploy/MenuScripts/openpype_menu.py index 4b5e8f91a0..20547b21f2 100644 --- a/openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py +++ b/openpype/hosts/fusion/deploy/MenuScripts/openpype_menu.py @@ -8,6 +8,11 @@ log = Logger().get_logger(__name__) def main(env): + # This script working directory starts in Fusion application folder. + # However the contents of that folder can conflict with Qt library dlls + # so we make sure to move out of it to avoid DLL Load Failed errors. + os.chdir("..") + import avalon.api from openpype.hosts.fusion import api from openpype.hosts.fusion.api import menu @@ -22,6 +27,11 @@ def main(env): menu.launch_openpype_menu() + # Initiate a QTimer to check if Fusion is still alive every X interval + # If Fusion is not found - kill itself + # todo(roy): Implement timer that ensures UI doesn't remain when e.g. + # Fusion closes down + if __name__ == "__main__": result = main(os.environ) diff --git a/openpype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/32bit/backgrounds_selected_to32bit.py similarity index 100% rename from openpype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/32bit/backgrounds_selected_to32bit.py diff --git a/openpype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/32bit/backgrounds_to32bit.py similarity index 100% rename from openpype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/32bit/backgrounds_to32bit.py diff --git a/openpype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/32bit/loaders_selected_to32bit.py similarity index 100% rename from openpype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/32bit/loaders_selected_to32bit.py diff --git a/openpype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/32bit/loaders_to32bit.py similarity index 100% rename from openpype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/32bit/loaders_to32bit.py diff --git a/openpype/hosts/fusion/utility_scripts/switch_ui.py b/openpype/hosts/fusion/deploy/Scripts/Comp/switch_ui.py similarity index 100% rename from openpype/hosts/fusion/utility_scripts/switch_ui.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/switch_ui.py diff --git a/openpype/hosts/fusion/utility_scripts/update_loader_ranges.py b/openpype/hosts/fusion/deploy/Scripts/Comp/update_loader_ranges.py similarity index 100% rename from openpype/hosts/fusion/utility_scripts/update_loader_ranges.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/update_loader_ranges.py diff --git a/openpype/hosts/fusion/deploy/fusion_shared.prefs b/openpype/hosts/fusion/deploy/fusion_shared.prefs new file mode 100644 index 0000000000..998c6a6d66 --- /dev/null +++ b/openpype/hosts/fusion/deploy/fusion_shared.prefs @@ -0,0 +1,19 @@ +{ +Locked = true, +Global = { + Paths = { + Map = { + ["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy", + ["Reactor:"] = "$(REACTOR)", + + ["Config:"] = "UserPaths:Config;OpenPype:Config", + ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:Scripts", + ["UserPaths:"] = "UserData:;AllData:;Fusion:;Reactor:Deploy" + }, + }, + Script = { + PythonVersion = 3, + Python3Forced = true + }, + }, +} \ No newline at end of file diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index e635a0ea74..c78d433e5c 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -1,8 +1,6 @@ import os -import shutil - -import openpype.hosts.fusion from openpype.lib import PreLaunchHook, ApplicationLaunchFailed +from openpype.hosts.fusion import HOST_DIR class FusionPrelaunch(PreLaunchHook): @@ -14,101 +12,33 @@ class FusionPrelaunch(PreLaunchHook): def execute(self): # making sure python 3.6 is installed at provided path - py36_dir = self.launch_context.env.get("PYTHON36") - if not py36_dir: - raise ApplicationLaunchFailed( - "Required environment variable \"PYTHON36\" is not set." - "\n\nFusion implementation requires to have" - " installed Python 3.6" - ) + py36_var = "FUSION16_PYTHON36_HOME" + fusion_python36_home = self.launch_context.env.get(py36_var, "") - py36_dir = os.path.normpath(py36_dir) - if not os.path.isdir(py36_dir): + self.log.info(f"Looking for Python 3.6 in: {fusion_python36_home}") + for path in fusion_python36_home.split(os.pathsep): + # Allow defining multiple paths to allow "fallback" to other + # path. But make to set only a single path as final variable. + py36_dir = os.path.normpath(path) + if os.path.isdir(py36_dir): + break + else: raise ApplicationLaunchFailed( "Python 3.6 is not installed at the provided path.\n" "Either make sure the environments in fusion settings has" " 'PYTHON36' set corectly or make sure Python 3.6 is installed" - f" in the given path.\n\nPYTHON36: {py36_dir}" - ) - self.log.info(f"Path to Fusion Python folder: '{py36_dir}'...") - self.launch_context.env["PYTHON36"] = py36_dir - - utility_dir = self.launch_context.env.get("FUSION_UTILITY_SCRIPTS_DIR") - if not utility_dir: - raise ApplicationLaunchFailed( - "Required Fusion utility script dir environment variable" - " \"FUSION_UTILITY_SCRIPTS_DIR\" is not set." + f" in the given path.\n\nPYTHON36: {fusion_python36_home}" ) - # setting utility scripts dir for scripts syncing - utility_dir = os.path.normpath(utility_dir) - if not os.path.isdir(utility_dir): - raise ApplicationLaunchFailed( - "Fusion utility script dir does not exist. Either make sure " - "the environments in fusion settings has" - " 'FUSION_UTILITY_SCRIPTS_DIR' set correctly or reinstall " - f"Fusion.\n\nFUSION_UTILITY_SCRIPTS_DIR: '{utility_dir}'" - ) + self.log.info(f"Setting {py36_var}: '{py36_dir}'...") + self.launch_context.env[py36_var] = py36_dir - self._sync_utility_scripts(self.launch_context.env) - self.log.info("Fusion Pype wrapper has been installed") + # Add our Fusion Master Prefs which is the only way to customize + # Fusion to define where it can read custom scripts and tools from + self.log.info(f"Setting OPENPYPE_FUSION: {HOST_DIR}") + self.launch_context.env["OPENPYPE_FUSION"] = HOST_DIR - def _sync_utility_scripts(self, env): - """ Synchronizing basic utlility scripts for resolve. - - To be able to run scripts from inside `Fusion/Workspace/Scripts` menu - all scripts has to be accessible from defined folder. - """ - if not env: - env = {k: v for k, v in os.environ.items()} - - # initiate inputs - scripts = {} - us_env = env.get("FUSION_UTILITY_SCRIPTS_SOURCE_DIR") - us_dir = env.get("FUSION_UTILITY_SCRIPTS_DIR", "") - us_paths = [os.path.join( - os.path.dirname(os.path.abspath(openpype.hosts.fusion.__file__)), - "utility_scripts" - )] - - # collect script dirs - if us_env: - self.log.info(f"Utility Scripts Env: `{us_env}`") - us_paths = us_env.split( - os.pathsep) + us_paths - - # collect scripts from dirs - for path in us_paths: - scripts.update({path: os.listdir(path)}) - - self.log.info(f"Utility Scripts Dir: `{us_paths}`") - self.log.info(f"Utility Scripts: `{scripts}`") - - # make sure no script file is in folder - if next((s for s in os.listdir(us_dir)), None): - for s in os.listdir(us_dir): - path = os.path.normpath( - os.path.join(us_dir, s)) - self.log.info(f"Removing `{path}`...") - - # remove file or directory if not in our folders - if not os.path.isdir(path): - os.remove(path) - else: - shutil.rmtree(path) - - # copy scripts into Resolve's utility scripts dir - for d, sl in scripts.items(): - # directory and scripts list - for s in sl: - # script in script list - src = os.path.normpath(os.path.join(d, s)) - dst = os.path.normpath(os.path.join(us_dir, s)) - - self.log.info(f"Copying `{src}` to `{dst}`...") - - # copy file or directory from our folders to fusion's folder - if not os.path.isdir(src): - shutil.copy2(src, dst) - else: - shutil.copytree(src, dst) + pref_var = "FUSION16_MasterPrefs" # used by both Fu16 and Fu17 + prefs = os.path.join(HOST_DIR, "deploy", "fusion_shared.prefs") + self.log.info(f"Setting {pref_var}: {prefs}") + self.launch_context.env[pref_var] = prefs diff --git a/openpype/hosts/fusion/plugins/load/actions.py b/openpype/hosts/fusion/plugins/load/actions.py index 6af99e4c56..84b66fc69a 100644 --- a/openpype/hosts/fusion/plugins/load/actions.py +++ b/openpype/hosts/fusion/plugins/load/actions.py @@ -11,6 +11,7 @@ class FusionSetFrameRangeLoader(api.Loader): families = ["animation", "camera", "imagesequence", + "render", "yeticache", "pointcache", "render"] @@ -45,6 +46,7 @@ class FusionSetFrameRangeWithHandlesLoader(api.Loader): families = ["animation", "camera", "imagesequence", + "render", "yeticache", "pointcache", "render"] diff --git a/openpype/hosts/fusion/scripts/fusion_switch_shot.py b/openpype/hosts/fusion/scripts/fusion_switch_shot.py index 041b53f6c9..9dd8a351e4 100644 --- a/openpype/hosts/fusion/scripts/fusion_switch_shot.py +++ b/openpype/hosts/fusion/scripts/fusion_switch_shot.py @@ -176,7 +176,7 @@ def update_frame_range(comp, representations): versions = list(versions) versions = [v for v in versions - if v["data"].get("startFrame", None) is not None] + if v["data"].get("frameStart", None) is not None] if not versions: log.warning("No versions loaded to match frame range to.\n") From e72c7680684172c399b1a07f346c9897ba75d508 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 17 Feb 2022 12:30:10 +0100 Subject: [PATCH 002/513] fusion: adding reset resolution (cherry picked from commit 209511ee3d938e21c20cff03edcdbbde4a4791f0) --- openpype/hosts/fusion/api/__init__.py | 4 +++- openpype/hosts/fusion/api/lib.py | 29 +++++++++++++++++++++++++-- openpype/hosts/fusion/api/menu.py | 23 +++++++++++++-------- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/fusion/api/__init__.py b/openpype/hosts/fusion/api/__init__.py index 19d1e092fe..78afabdb45 100644 --- a/openpype/hosts/fusion/api/__init__.py +++ b/openpype/hosts/fusion/api/__init__.py @@ -23,7 +23,8 @@ from .workio import ( from .lib import ( maintained_selection, get_additional_data, - update_frame_range + update_frame_range, + set_framerange ) from .menu import launch_openpype_menu @@ -53,6 +54,7 @@ __all__ = [ "maintained_selection", "get_additional_data", "update_frame_range", + "set_framerange", # menu "launch_openpype_menu", diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 5d97f83032..37a13e4a10 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -8,12 +8,14 @@ from Qt import QtGui import avalon.api from avalon import io from .pipeline import get_current_comp, comp_lock_and_undo_chunk - +from openpype.api import ( + get_asset +) self = sys.modules[__name__] self._project = None -def update_frame_range(start, end, comp=None, set_render_range=True): +def update_frame_range(start, end, comp=None, set_render_range=True, **kwargs): """Set Fusion comp's start and end frame range Args: @@ -22,6 +24,7 @@ def update_frame_range(start, end, comp=None, set_render_range=True): comp (object, Optional): comp object from fusion set_render_range (bool, Optional): When True this will also set the composition's render start and end frame. + kwargs (dict): additional kwargs Returns: None @@ -36,6 +39,16 @@ def update_frame_range(start, end, comp=None, set_render_range=True): "COMPN_GlobalEnd": end } + # exclude handles if any found in kwargs + if kwargs.get("handle_start"): + handle_start = kwargs.get("handle_start") + attrs["COMPN_GlobalStart"] = int(start - handle_start) + + if kwargs.get("handle_end"): + handle_end = kwargs.get("handle_end") + attrs["COMPN_GlobalEnd"] = int(end + handle_end) + + # set frame range if set_render_range: attrs.update({ "COMPN_RenderStart": start, @@ -46,6 +59,18 @@ def update_frame_range(start, end, comp=None, set_render_range=True): comp.SetAttrs(attrs) +def set_framerange(): + asset_doc = get_asset() + start = asset_doc["data"]["frameStart"] + end = asset_doc["data"]["frameEnd"] + + data = { + "handle_start": asset_doc["data"]["handleStart"], + "handle_end": asset_doc["data"]["handleEnd"] + } + update_frame_range(start, end, set_render_range=True, **data) + + def get_additional_data(container): """Get Fusion related data for the container diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 7799528462..31a3b5b88c 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -1,4 +1,3 @@ -import os import sys from Qt import QtWidgets, QtCore @@ -11,7 +10,9 @@ from openpype.hosts.fusion.scripts import ( set_rendermode, duplicate_with_inputs ) - +from openpype.hosts.fusion.api import ( + set_framerange +) class Spacer(QtWidgets.QWidget): def __init__(self, height, *args, **kwargs): @@ -61,12 +62,11 @@ class OpenPypeMenu(QtWidgets.QWidget): manager_btn = QtWidgets.QPushButton("Manage...", self) libload_btn = QtWidgets.QPushButton("Library...", self) rendermode_btn = QtWidgets.QPushButton("Set render mode...", self) + set_framerange_btn = QtWidgets.QPushButton("Set Frame Range", self) + set_resolution_btn = QtWidgets.QPushButton("Set Resolution", self) duplicate_with_inputs_btn = QtWidgets.QPushButton( "Duplicate with input connections", self ) - reset_resolution_btn = QtWidgets.QPushButton( - "Reset Resolution from project", self - ) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(10, 20, 10, 20) @@ -90,12 +90,13 @@ class OpenPypeMenu(QtWidgets.QWidget): layout.addWidget(Spacer(15, self)) + layout.addWidget(set_framerange_btn) + layout.addWidget(set_resolution_btn) layout.addWidget(rendermode_btn) layout.addWidget(Spacer(15, self)) layout.addWidget(duplicate_with_inputs_btn) - layout.addWidget(reset_resolution_btn) self.setLayout(layout) @@ -111,7 +112,8 @@ class OpenPypeMenu(QtWidgets.QWidget): rendermode_btn.clicked.connect(self.on_rendernode_clicked) duplicate_with_inputs_btn.clicked.connect( self.on_duplicate_with_inputs_clicked) - reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) + set_resolution_btn.clicked.connect(self.on_set_resolution_clicked) + set_framerange_btn.clicked.connect(self.on_set_framerange_clicked) self._callbacks = [] self.register_callback("taskChanged", self.on_task_changed) @@ -171,9 +173,14 @@ class OpenPypeMenu(QtWidgets.QWidget): duplicate_with_inputs.duplicate_with_input_connections() print("Clicked Set Colorspace") - def on_reset_resolution_clicked(self): + def on_set_resolution_clicked(self): print("Clicked Reset Resolution") + def on_set_framerange_clicked(self): + print("Clicked Reset Framerange") + set_framerange() + + def launch_openpype_menu(): app = QtWidgets.QApplication(sys.argv) From 1b40d5102fac1f726a03a6f5d08fcd3a519f16c2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 22:22:50 +0100 Subject: [PATCH 003/513] Fix hound issues --- openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py b/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py index 4fcdb7658f..ab9f13ce05 100644 --- a/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py +++ b/openpype/hosts/fusion/deploy/MenuScripts/install_pyside2.py @@ -6,14 +6,14 @@ import importlib try: - from Qt import QtWidgets + from Qt import QtWidgets # noqa: F401 from Qt import __binding__ print(f"Qt binding: {__binding__}") mod = importlib.import_module(__binding__) print(f"Qt path: {mod.__file__}") print("Qt library found, nothing to do..") -except ImportError as exc: +except ImportError: print("Assuming no Qt library is installed..") print('Installing PySide2 for Python 3.6: ' f'{os.environ["FUSION16_PYTHON36_HOME"]}') From 43be2004c7e14a272aa512df9bcc4916c5dac8a4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 22:24:42 +0100 Subject: [PATCH 004/513] Avoid assigning a lambda expression --- openpype/hosts/fusion/api/menu.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 31a3b5b88c..1d7e1092da 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -14,6 +14,7 @@ from openpype.hosts.fusion.api import ( set_framerange ) + class Spacer(QtWidgets.QWidget): def __init__(self, height, *args, **kwargs): super(Spacer, self).__init__(*args, **kwargs) @@ -128,9 +129,11 @@ class OpenPypeMenu(QtWidgets.QWidget): # Create a wrapper callback that we only store # for as long as we want it to persist as callback - callback = lambda *args: fn() - self._callbacks.append(callback) - api.on(name, callback) + def _callback(*args): + fn() + + self._callbacks.append(_callback) + api.on(name, _callback) def deregister_all_callbacks(self): self._callbacks[:] = [] From 860f159ec369fedd423181ce0342d9b19c91db8f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 22:25:59 +0100 Subject: [PATCH 005/513] Fix double "render" family --- openpype/hosts/fusion/plugins/load/actions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/fusion/plugins/load/actions.py b/openpype/hosts/fusion/plugins/load/actions.py index 84b66fc69a..6af99e4c56 100644 --- a/openpype/hosts/fusion/plugins/load/actions.py +++ b/openpype/hosts/fusion/plugins/load/actions.py @@ -11,7 +11,6 @@ class FusionSetFrameRangeLoader(api.Loader): families = ["animation", "camera", "imagesequence", - "render", "yeticache", "pointcache", "render"] @@ -46,7 +45,6 @@ class FusionSetFrameRangeWithHandlesLoader(api.Loader): families = ["animation", "camera", "imagesequence", - "render", "yeticache", "pointcache", "render"] From cdca18e93f53ba1e222d52d90fecebf0e7feff01 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 20:58:34 +0100 Subject: [PATCH 006/513] Fusion: Add support for open last workfile --- openpype/hooks/pre_add_last_workfile_arg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 653f97b3dd..eb9e6a6b1c 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -18,6 +18,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "nukex", "hiero", "nukestudio", + "fusion", "blender", "photoshop", "tvpaint", From 4e614c8337be7ea35ce4a063385d844d90c1e896 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 21:02:43 +0100 Subject: [PATCH 007/513] Fix typo + wrong label --- openpype/hosts/fusion/api/menu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 1d7e1092da..7b23e2bc2b 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -110,7 +110,7 @@ class OpenPypeMenu(QtWidgets.QWidget): load_btn.clicked.connect(self.on_load_clicked) manager_btn.clicked.connect(self.on_manager_clicked) libload_btn.clicked.connect(self.on_libload_clicked) - rendermode_btn.clicked.connect(self.on_rendernode_clicked) + rendermode_btn.clicked.connect(self.on_rendermode_clicked) duplicate_with_inputs_btn.clicked.connect( self.on_duplicate_with_inputs_clicked) set_resolution_btn.clicked.connect(self.on_set_resolution_clicked) @@ -162,7 +162,7 @@ class OpenPypeMenu(QtWidgets.QWidget): print("Clicked Library") host_tools.show_library_loader() - def on_rendernode_clicked(self): + def on_rendermode_clicked(self): print("Clicked Set Render Mode") if self.render_mode_widget is None: window = set_rendermode.SetRenderMode() @@ -173,8 +173,8 @@ class OpenPypeMenu(QtWidgets.QWidget): self.render_mode_widget.show() def on_duplicate_with_inputs_clicked(self): + print("Clicked Duplicate with input connections") duplicate_with_inputs.duplicate_with_input_connections() - print("Clicked Set Colorspace") def on_set_resolution_clicked(self): print("Clicked Reset Resolution") From 959aa2a47974075d97be41dc5c49fb6ee2641875 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 21:10:30 +0100 Subject: [PATCH 008/513] Remove duplicate script that has more recent version in openpype/hosts/fusion/scripts --- openpype/scripts/fusion_switch_shot.py | 248 ------------------------- 1 file changed, 248 deletions(-) delete mode 100644 openpype/scripts/fusion_switch_shot.py diff --git a/openpype/scripts/fusion_switch_shot.py b/openpype/scripts/fusion_switch_shot.py deleted file mode 100644 index 26f5356336..0000000000 --- a/openpype/scripts/fusion_switch_shot.py +++ /dev/null @@ -1,248 +0,0 @@ -import os -import re -import sys -import logging - -# Pipeline imports -from avalon import api, io, pipeline -import avalon.fusion - -# Config imports -import openpype.lib as pype -import openpype.hosts.fusion.lib as fusion_lib - -log = logging.getLogger("Update Slap Comp") - -self = sys.modules[__name__] -self._project = None - - -def _format_version_folder(folder): - """Format a version folder based on the filepath - - Assumption here is made that, if the path does not exists the folder - will be "v001" - - Args: - folder: file path to a folder - - Returns: - str: new version folder name - """ - - new_version = 1 - if os.path.isdir(folder): - re_version = re.compile("v\d+$") - versions = [i for i in os.listdir(folder) if os.path.isdir(i) - and re_version.match(i)] - if versions: - # ensure the "v" is not included - new_version = int(max(versions)[1:]) + 1 - - version_folder = "v{:03d}".format(new_version) - - return version_folder - - -def _get_work_folder(session): - """Convenience function to get the work folder path of the current asset""" - - # Get new filename, create path based on asset and work template - template_work = self._project["config"]["template"]["work"] - work_path = pipeline._format_work_template(template_work, session) - - return os.path.normpath(work_path) - - -def _get_fusion_instance(): - fusion = getattr(sys.modules["__main__"], "fusion", None) - if fusion is None: - try: - # Support for FuScript.exe, BlackmagicFusion module for py2 only - import BlackmagicFusion as bmf - fusion = bmf.scriptapp("Fusion") - except ImportError: - raise RuntimeError("Could not find a Fusion instance") - return fusion - - -def _format_filepath(session): - - project = session["AVALON_PROJECT"] - asset = session["AVALON_ASSET"] - - # Save updated slap comp - work_path = _get_work_folder(session) - walk_to_dir = os.path.join(work_path, "scenes", "slapcomp") - slapcomp_dir = os.path.abspath(walk_to_dir) - - # Ensure destination exists - if not os.path.isdir(slapcomp_dir): - log.warning("Folder did not exist, creating folder structure") - os.makedirs(slapcomp_dir) - - # Compute output path - new_filename = "{}_{}_slapcomp_v001.comp".format(project, asset) - new_filepath = os.path.join(slapcomp_dir, new_filename) - - # Create new unqiue filepath - if os.path.exists(new_filepath): - new_filepath = pype.version_up(new_filepath) - - return new_filepath - - -def _update_savers(comp, session): - """Update all savers of the current comp to ensure the output is correct - - Args: - comp (object): current comp instance - session (dict): the current Avalon session - - Returns: - None - """ - - new_work = _get_work_folder(session) - renders = os.path.join(new_work, "renders") - version_folder = _format_version_folder(renders) - renders_version = os.path.join(renders, version_folder) - - comp.Print("New renders to: %s\n" % renders) - - with avalon.fusion.comp_lock_and_undo_chunk(comp): - savers = comp.GetToolList(False, "Saver").values() - for saver in savers: - filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0] - filename = os.path.basename(filepath) - new_path = os.path.join(renders_version, filename) - saver["Clip"] = new_path - - -def update_frame_range(comp, representations): - """Update the frame range of the comp and render length - - The start and end frame are based on the lowest start frame and the highest - end frame - - Args: - comp (object): current focused comp - representations (list) collection of dicts - - Returns: - None - - """ - - version_ids = [r["parent"] for r in representations] - versions = io.find({"type": "version", "_id": {"$in": version_ids}}) - versions = list(versions) - - start = min(v["data"]["frameStart"] for v in versions) - end = max(v["data"]["frameEnd"] for v in versions) - - fusion_lib.update_frame_range(start, end, comp=comp) - - -def switch(asset_name, filepath=None, new=True): - """Switch the current containers of the file to the other asset (shot) - - Args: - filepath (str): file path of the comp file - asset_name (str): name of the asset (shot) - new (bool): Save updated comp under a different name - - Returns: - comp path (str): new filepath of the updated comp - - """ - - # If filepath provided, ensure it is valid absolute path - if filepath is not None: - if not os.path.isabs(filepath): - filepath = os.path.abspath(filepath) - - assert os.path.exists(filepath), "%s must exist " % filepath - - # Assert asset name exists - # It is better to do this here then to wait till switch_shot does it - asset = io.find_one({"type": "asset", "name": asset_name}) - assert asset, "Could not find '%s' in the database" % asset_name - - # Get current project - self._project = io.find_one({ - "type": "project", - "name": api.Session["AVALON_PROJECT"] - }) - - # Go to comp - if not filepath: - current_comp = avalon.fusion.get_current_comp() - assert current_comp is not None, "Could not find current comp" - else: - fusion = _get_fusion_instance() - current_comp = fusion.LoadComp(filepath, quiet=True) - assert current_comp is not None, "Fusion could not load '%s'" % filepath - - host = api.registered_host() - containers = list(host.ls()) - assert containers, "Nothing to update" - - representations = [] - for container in containers: - try: - representation = fusion_lib.switch_item(container, - asset_name=asset_name) - representations.append(representation) - except Exception as e: - current_comp.Print("Error in switching! %s\n" % e.message) - - message = "Switched %i Loaders of the %i\n" % (len(representations), - len(containers)) - current_comp.Print(message) - - # Build the session to switch to - switch_to_session = api.Session.copy() - switch_to_session["AVALON_ASSET"] = asset['name'] - - if new: - comp_path = _format_filepath(switch_to_session) - - # Update savers output based on new session - _update_savers(current_comp, switch_to_session) - else: - comp_path = pype.version_up(filepath) - - current_comp.Print(comp_path) - - current_comp.Print("\nUpdating frame range") - update_frame_range(current_comp, representations) - - current_comp.Save(comp_path) - - return comp_path - - -if __name__ == '__main__': - - import argparse - - parser = argparse.ArgumentParser(description="Switch to a shot within an" - "existing comp file") - - parser.add_argument("--file_path", - type=str, - default=True, - help="File path of the comp to use") - - parser.add_argument("--asset_name", - type=str, - default=True, - help="Name of the asset (shot) to switch") - - args, unknown = parser.parse_args() - - api.install(avalon.fusion) - switch(args.asset_name, args.file_path) - - sys.exit(0) From 51ef4ce4e5de954bde31ac634ecb1553b7ceee62 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 21:11:40 +0100 Subject: [PATCH 009/513] Move Comp scripts to an OpenPype submenu to clarify those are OpenPype scripts --- .../Comp/{ => OpenPype}/32bit/backgrounds_selected_to32bit.py | 0 .../Scripts/Comp/{ => OpenPype}/32bit/backgrounds_to32bit.py | 0 .../Scripts/Comp/{ => OpenPype}/32bit/loaders_selected_to32bit.py | 0 .../deploy/Scripts/Comp/{ => OpenPype}/32bit/loaders_to32bit.py | 0 .../hosts/fusion/deploy/Scripts/Comp/{ => OpenPype}/switch_ui.py | 0 .../deploy/Scripts/Comp/{ => OpenPype}/update_loader_ranges.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/fusion/deploy/Scripts/Comp/{ => OpenPype}/32bit/backgrounds_selected_to32bit.py (100%) rename openpype/hosts/fusion/deploy/Scripts/Comp/{ => OpenPype}/32bit/backgrounds_to32bit.py (100%) rename openpype/hosts/fusion/deploy/Scripts/Comp/{ => OpenPype}/32bit/loaders_selected_to32bit.py (100%) rename openpype/hosts/fusion/deploy/Scripts/Comp/{ => OpenPype}/32bit/loaders_to32bit.py (100%) rename openpype/hosts/fusion/deploy/Scripts/Comp/{ => OpenPype}/switch_ui.py (100%) rename openpype/hosts/fusion/deploy/Scripts/Comp/{ => OpenPype}/update_loader_ranges.py (100%) diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/32bit/backgrounds_selected_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_selected_to32bit.py similarity index 100% rename from openpype/hosts/fusion/deploy/Scripts/Comp/32bit/backgrounds_selected_to32bit.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_selected_to32bit.py diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/32bit/backgrounds_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_to32bit.py similarity index 100% rename from openpype/hosts/fusion/deploy/Scripts/Comp/32bit/backgrounds_to32bit.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/backgrounds_to32bit.py diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/32bit/loaders_selected_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_selected_to32bit.py similarity index 100% rename from openpype/hosts/fusion/deploy/Scripts/Comp/32bit/loaders_selected_to32bit.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_selected_to32bit.py diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/32bit/loaders_to32bit.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_to32bit.py similarity index 100% rename from openpype/hosts/fusion/deploy/Scripts/Comp/32bit/loaders_to32bit.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/32bit/loaders_to32bit.py diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/switch_ui.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py similarity index 100% rename from openpype/hosts/fusion/deploy/Scripts/Comp/switch_ui.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/switch_ui.py diff --git a/openpype/hosts/fusion/deploy/Scripts/Comp/update_loader_ranges.py b/openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/update_loader_ranges.py similarity index 100% rename from openpype/hosts/fusion/deploy/Scripts/Comp/update_loader_ranges.py rename to openpype/hosts/fusion/deploy/Scripts/Comp/OpenPype/update_loader_ranges.py From 2f9eb8fa640226aafbe2729b5417d1db016cc56d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 21:32:07 +0100 Subject: [PATCH 010/513] Fix on_pyblish_instance_toggled arguments --- openpype/hosts/fusion/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 64dda0bc8a..0c1c5a4362 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -97,7 +97,7 @@ def uninstall(): ) -def on_pyblish_instance_toggled(instance, new_value, old_value): +def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle saver tool passthrough states on instance toggles.""" comp = instance.context.data.get("currentComp") if not comp: From 45391662f7a6acc33b4d618f8a7453c1186cdd89 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 17 Mar 2022 12:21:12 +0100 Subject: [PATCH 011/513] Fix merge conflict and remove fusion_switch_shot.py again --- .../fusion/scripts/fusion_switch_shot.py | 285 ------------------ 1 file changed, 285 deletions(-) delete mode 100644 openpype/hosts/fusion/scripts/fusion_switch_shot.py diff --git a/openpype/hosts/fusion/scripts/fusion_switch_shot.py b/openpype/hosts/fusion/scripts/fusion_switch_shot.py deleted file mode 100644 index ca7efb9136..0000000000 --- a/openpype/hosts/fusion/scripts/fusion_switch_shot.py +++ /dev/null @@ -1,285 +0,0 @@ -import os -import re -import sys -import logging - -# Pipeline imports -import avalon.api -from avalon import io - -from openpype.lib import version_up -from openpype.hosts.fusion import api -from openpype.hosts.fusion.api import lib -from openpype.lib.avalon_context import get_workdir_from_session - -log = logging.getLogger("Update Slap Comp") - -self = sys.modules[__name__] -self._project = None - - -def _format_version_folder(folder): - """Format a version folder based on the filepath - - Assumption here is made that, if the path does not exists the folder - will be "v001" - - Args: - folder: file path to a folder - - Returns: - str: new version folder name - """ - - new_version = 1 - if os.path.isdir(folder): - re_version = re.compile(r"v\d+$") - versions = [i for i in os.listdir(folder) if os.path.isdir(i) - and re_version.match(i)] - if versions: - # ensure the "v" is not included - new_version = int(max(versions)[1:]) + 1 - - version_folder = "v{:03d}".format(new_version) - - return version_folder - - -def _get_fusion_instance(): - fusion = getattr(sys.modules["__main__"], "fusion", None) - if fusion is None: - try: - # Support for FuScript.exe, BlackmagicFusion module for py2 only - import BlackmagicFusion as bmf - fusion = bmf.scriptapp("Fusion") - except ImportError: - raise RuntimeError("Could not find a Fusion instance") - return fusion - - -def _format_filepath(session): - - project = session["AVALON_PROJECT"] - asset = session["AVALON_ASSET"] - - # Save updated slap comp - work_path = get_workdir_from_session(session) - walk_to_dir = os.path.join(work_path, "scenes", "slapcomp") - slapcomp_dir = os.path.abspath(walk_to_dir) - - # Ensure destination exists - if not os.path.isdir(slapcomp_dir): - log.warning("Folder did not exist, creating folder structure") - os.makedirs(slapcomp_dir) - - # Compute output path - new_filename = "{}_{}_slapcomp_v001.comp".format(project, asset) - new_filepath = os.path.join(slapcomp_dir, new_filename) - - # Create new unique filepath - if os.path.exists(new_filepath): - new_filepath = version_up(new_filepath) - - return new_filepath - - -def _update_savers(comp, session): - """Update all savers of the current comp to ensure the output is correct - - This will refactor the Saver file outputs to the renders of the new session - that is provided. - - In the case the original saver path had a path set relative to a /fusion/ - folder then that relative path will be matched with the exception of all - "version" (e.g. v010) references will be reset to v001. Otherwise only a - version folder will be computed in the new session's work "render" folder - to dump the files in and keeping the original filenames. - - Args: - comp (object): current comp instance - session (dict): the current Avalon session - - Returns: - None - """ - - new_work = get_workdir_from_session(session) - renders = os.path.join(new_work, "renders") - version_folder = _format_version_folder(renders) - renders_version = os.path.join(renders, version_folder) - - comp.Print("New renders to: %s\n" % renders) - - with api.comp_lock_and_undo_chunk(comp): - savers = comp.GetToolList(False, "Saver").values() - for saver in savers: - filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0] - - # Get old relative path to the "fusion" app folder so we can apply - # the same relative path afterwards. If not found fall back to - # using just a version folder with the filename in it. - # todo: can we make this less magical? - relpath = filepath.replace("\\", "/").rsplit("/fusion/", 1)[-1] - - if os.path.isabs(relpath): - # If not relative to a "/fusion/" folder then just use filename - filename = os.path.basename(filepath) - log.warning("Can't parse relative path, refactoring to only" - "filename in a version folder: %s" % filename) - new_path = os.path.join(renders_version, filename) - - else: - # Else reuse the relative path - # Reset version in folder and filename in the relative path - # to v001. The version should be is only detected when prefixed - # with either `_v` (underscore) or `/v` (folder) - version_pattern = r"(/|_)v[0-9]+" - if re.search(version_pattern, relpath): - new_relpath = re.sub(version_pattern, - r"\1v001", - relpath) - log.info("Resetting version folders to v001: " - "%s -> %s" % (relpath, new_relpath)) - relpath = new_relpath - - new_path = os.path.join(new_work, relpath) - - saver["Clip"] = new_path - - -def update_frame_range(comp, representations): - """Update the frame range of the comp and render length - - The start and end frame are based on the lowest start frame and the highest - end frame - - Args: - comp (object): current focused comp - representations (list) collection of dicts - - Returns: - None - - """ - - version_ids = [r["parent"] for r in representations] - versions = io.find({"type": "version", "_id": {"$in": version_ids}}) - versions = list(versions) - - versions = [v for v in versions - if v["data"].get("frameStart", None) is not None] - - if not versions: - log.warning("No versions loaded to match frame range to.\n") - return - - start = min(v["data"]["frameStart"] for v in versions) - end = max(v["data"]["frameEnd"] for v in versions) - - lib.update_frame_range(start, end, comp=comp) - - -def switch(asset_name, filepath=None, new=True): - """Switch the current containers of the file to the other asset (shot) - - Args: - filepath (str): file path of the comp file - asset_name (str): name of the asset (shot) - new (bool): Save updated comp under a different name - - Returns: - comp path (str): new filepath of the updated comp - - """ - - # If filepath provided, ensure it is valid absolute path - if filepath is not None: - if not os.path.isabs(filepath): - filepath = os.path.abspath(filepath) - - assert os.path.exists(filepath), "%s must exist " % filepath - - # Assert asset name exists - # It is better to do this here then to wait till switch_shot does it - asset = io.find_one({"type": "asset", "name": asset_name}) - assert asset, "Could not find '%s' in the database" % asset_name - - # Get current project - self._project = io.find_one({"type": "project", - "name": avalon.api.Session["AVALON_PROJECT"]}) - - # Go to comp - if not filepath: - current_comp = api.get_current_comp() - assert current_comp is not None, "Could not find current comp" - else: - fusion = _get_fusion_instance() - current_comp = fusion.LoadComp(filepath, quiet=True) - assert current_comp is not None, ( - "Fusion could not load '{}'").format(filepath) - - host = avalon.api.registered_host() - containers = list(host.ls()) - assert containers, "Nothing to update" - - representations = [] - for container in containers: - try: - representation = lib.switch_item( - container, - asset_name=asset_name) - representations.append(representation) - except Exception as e: - current_comp.Print("Error in switching! %s\n" % e.message) - - message = "Switched %i Loaders of the %i\n" % (len(representations), - len(containers)) - current_comp.Print(message) - - # Build the session to switch to - switch_to_session = avalon.api.Session.copy() - switch_to_session["AVALON_ASSET"] = asset['name'] - - if new: - comp_path = _format_filepath(switch_to_session) - - # Update savers output based on new session - _update_savers(current_comp, switch_to_session) - else: - comp_path = version_up(filepath) - - current_comp.Print(comp_path) - - current_comp.Print("\nUpdating frame range") - update_frame_range(current_comp, representations) - - current_comp.Save(comp_path) - - return comp_path - - -if __name__ == '__main__': - - # QUESTION: can we convert this to gui rather then standalone script? - # TODO: convert to gui tool - import argparse - - parser = argparse.ArgumentParser(description="Switch to a shot within an" - "existing comp file") - - parser.add_argument("--file_path", - type=str, - default=True, - help="File path of the comp to use") - - parser.add_argument("--asset_name", - type=str, - default=True, - help="Name of the asset (shot) to switch") - - args, unknown = parser.parse_args() - - avalon.api.install(api) - switch(args.asset_name, args.file_path) - - sys.exit(0) From 0941c186dffb75759f59fa2ca46bdde0fd2dc9c3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Mar 2022 19:44:42 +0200 Subject: [PATCH 012/513] Use new OpenPype Event System implemented with #2846 --- openpype/hosts/fusion/api/menu.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 7b23e2bc2b..b3d8e203c3 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -6,6 +6,7 @@ from avalon import api from openpype.tools.utils import host_tools from openpype.style import load_stylesheet +from openpype.lib import register_event_callback from openpype.hosts.fusion.scripts import ( set_rendermode, duplicate_with_inputs @@ -133,7 +134,7 @@ class OpenPypeMenu(QtWidgets.QWidget): fn() self._callbacks.append(_callback) - api.on(name, _callback) + register_event_callback(name, _callback) def deregister_all_callbacks(self): self._callbacks[:] = [] From 56892878c97b3244ef313cbd644241679773ecd3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 30 Mar 2022 12:21:58 +0200 Subject: [PATCH 013/513] Cosmetics --- openpype/hosts/fusion/api/menu.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index b3d8e203c3..42dacfa0c0 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -185,7 +185,6 @@ class OpenPypeMenu(QtWidgets.QWidget): set_framerange() - def launch_openpype_menu(): app = QtWidgets.QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) From 2d4061771d809c6d240e13965e845f04696ac0f9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 30 Mar 2022 14:45:11 +0200 Subject: [PATCH 014/513] Shut down fusionscript process when OpenPype menu gets closed along with other Qt windows --- openpype/hosts/fusion/api/menu.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 42dacfa0c0..4a646c5e8f 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -187,7 +187,6 @@ class OpenPypeMenu(QtWidgets.QWidget): def launch_openpype_menu(): app = QtWidgets.QApplication(sys.argv) - app.setQuitOnLastWindowClosed(False) pype_menu = OpenPypeMenu() @@ -196,4 +195,6 @@ def launch_openpype_menu(): pype_menu.show() - sys.exit(app.exec_()) + result = app.exec_() + print("Shutting down..") + sys.exit(result) From 53382a1960e23dbc80235f9d0f25c79e8dff6d06 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 30 Mar 2022 16:02:43 +0200 Subject: [PATCH 015/513] Draft implementation of Fusion heartbeat/pulse so UI closes after Fusion is closed --- openpype/hosts/fusion/api/menu.py | 6 +++ openpype/hosts/fusion/api/pulse.py | 62 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 openpype/hosts/fusion/api/pulse.py diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 4a646c5e8f..823670b9cf 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -15,6 +15,8 @@ from openpype.hosts.fusion.api import ( set_framerange ) +from .pulse import FusionPulse + class Spacer(QtWidgets.QWidget): def __init__(self, height, *args, **kwargs): @@ -121,6 +123,10 @@ class OpenPypeMenu(QtWidgets.QWidget): self.register_callback("taskChanged", self.on_task_changed) self.on_task_changed() + # Force close current process if Fusion is closed + self._pulse = FusionPulse(parent=self) + self._pulse.start() + def on_task_changed(self): # Update current context label label = api.Session["AVALON_ASSET"] diff --git a/openpype/hosts/fusion/api/pulse.py b/openpype/hosts/fusion/api/pulse.py new file mode 100644 index 0000000000..cad1c74e13 --- /dev/null +++ b/openpype/hosts/fusion/api/pulse.py @@ -0,0 +1,62 @@ +import os +import sys + +from Qt import QtCore, QtWidgets + + +class PulseThread(QtCore.QThread): + no_response = QtCore.Signal() + + def __init__(self, parent=None): + super(PulseThread, self).__init__(parent=parent) + + # Interval in milliseconds + self._interval = os.environ.get("OPENPYPE_FUSION_PULSE_INTERVAL", 1000) + + def run(self): + app = getattr(sys.modules["__main__"], "app", None) + + while True: + if self.isInterruptionRequested(): + return + try: + app.Test() + except Exception: + self.no_response.emit() + + self.msleep(self._interval) + + +class FusionPulse(QtCore.QObject): + """A Timer that checks whether host app is still alive. + + This checks whether the Fusion process is still active at a certain + interval. This is useful due to how Fusion runs its scripts. Each script + runs in its own environment and process (a `fusionscript` process each). + If Fusion would go down and we have a UI process running at the same time + then it can happen that the `fusionscript.exe` will remain running in the + background in limbo due to e.g. a Qt interface's QApplication that keeps + running infinitely. + + Warning: + When the host is not detected this will automatically exit + the current process. + + """ + + def __init__(self, parent=None): + super(FusionPulse, self).__init__(parent=parent) + self._thread = PulseThread(parent=self) + self._thread.no_response.connect(self.on_no_response) + + def on_no_response(self): + print("Pulse detected no response from Fusion..") + sys.exit(1) + + def start(self): + self._thread.start() + + def stop(self): + self._thread.requestInterruption() + + From 84ab53664f794314513fd6530890c4a1f5fca34b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 30 Mar 2022 16:11:35 +0200 Subject: [PATCH 016/513] Take the `interval` variable into the run thread - not sure if really better --- openpype/hosts/fusion/api/pulse.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/api/pulse.py b/openpype/hosts/fusion/api/pulse.py index cad1c74e13..6d448a31c9 100644 --- a/openpype/hosts/fusion/api/pulse.py +++ b/openpype/hosts/fusion/api/pulse.py @@ -10,12 +10,12 @@ class PulseThread(QtCore.QThread): def __init__(self, parent=None): super(PulseThread, self).__init__(parent=parent) - # Interval in milliseconds - self._interval = os.environ.get("OPENPYPE_FUSION_PULSE_INTERVAL", 1000) - def run(self): app = getattr(sys.modules["__main__"], "app", None) + # Interval in milliseconds + interval = os.environ.get("OPENPYPE_FUSION_PULSE_INTERVAL", 1000) + while True: if self.isInterruptionRequested(): return From 0e9dd34b3d015a6d2ea96f32820aad378ecf09dd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 30 Mar 2022 21:32:51 +0200 Subject: [PATCH 017/513] Fix code --- openpype/hosts/fusion/api/pulse.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/fusion/api/pulse.py b/openpype/hosts/fusion/api/pulse.py index 6d448a31c9..5b61f3bd63 100644 --- a/openpype/hosts/fusion/api/pulse.py +++ b/openpype/hosts/fusion/api/pulse.py @@ -1,7 +1,7 @@ import os import sys -from Qt import QtCore, QtWidgets +from Qt import QtCore class PulseThread(QtCore.QThread): @@ -24,7 +24,7 @@ class PulseThread(QtCore.QThread): except Exception: self.no_response.emit() - self.msleep(self._interval) + self.msleep(interval) class FusionPulse(QtCore.QObject): @@ -58,5 +58,3 @@ class FusionPulse(QtCore.QObject): def stop(self): self._thread.requestInterruption() - - From 0aeded448613e0faddbeb09a711456dada9e0826 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Aug 2022 16:04:31 +0200 Subject: [PATCH 018/513] Add collectors for input links for Maya + Fusion --- .../fusion/plugins/publish/collect_inputs.py | 112 +++++++++ openpype/hosts/maya/api/lib_rendersetup.py | 68 ++++++ .../maya/plugins/publish/collect_inputs.py | 214 ++++++++++++++++++ 3 files changed, 394 insertions(+) create mode 100644 openpype/hosts/fusion/plugins/publish/collect_inputs.py create mode 100644 openpype/hosts/maya/plugins/publish/collect_inputs.py diff --git a/openpype/hosts/fusion/plugins/publish/collect_inputs.py b/openpype/hosts/fusion/plugins/publish/collect_inputs.py new file mode 100644 index 0000000000..e610575e3a --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/collect_inputs.py @@ -0,0 +1,112 @@ +import pyblish.api + +from openpype.pipeline import registered_host + + +def collect_input_containers(tools): + """Collect containers that contain any of the node in `nodes`. + + This will return any loaded Avalon container that contains at least one of + the nodes. As such, the Avalon container is an input for it. Or in short, + there are member nodes of that container. + + Returns: + list: Input avalon containers + + """ + + # Lookup by node ids + lookup = frozenset([tool.Name for tool in tools]) + + containers = [] + host = registered_host() + for container in host.ls(): + + name = container["_tool"].Name + + # We currently assume no "groups" as containers but just single tools + # like a single "Loader" operator. As such we just check whether the + # Loader is part of the processing queue. + if name in lookup: + containers.append(container) + + return containers + + +def iter_upstream(tool): + """Yields all upstream inputs for the current tool. + + Yields: + tool: The input tools. + + """ + + def get_connected_input_tools(tool): + """Helper function that returns connected input tools for a tool.""" + inputs = [] + + # Filter only to actual types that will have sensible upstream + # connections. So we ignore just "Number" inputs as they can be + # many to iterate, slowing things down quite a bit - and in practice + # they don't have upstream connections. + VALID_INPUT_TYPES = ['Image', 'Particles', 'Mask', 'DataType3D'] + for type_ in VALID_INPUT_TYPES: + for input_ in tool.GetInputList(type_).values(): + output = input_.GetConnectedOutput() + if output: + input_tool = output.GetTool() + inputs.append(input_tool) + + return inputs + + # Initialize process queue with the node's inputs itself + queue = get_connected_input_tools(tool) + + # We keep track of which node names we have processed so far, to ensure we + # don't process the same hierarchy again. We are not pushing the tool + # itself into the set as that doesn't correctly recognize the same tool. + # Since tool names are unique in a comp in Fusion we rely on that. + collected = set(tool.Name for tool in queue) + + # Traverse upstream references for all nodes and yield them as we + # process the queue. + while queue: + upstream_tool = queue.pop() + yield upstream_tool + + # Find upstream tools that are not collected yet. + upstream_inputs = get_connected_input_tools(upstream_tool) + upstream_inputs = [t for t in upstream_inputs if + t.Name not in collected] + + queue.extend(upstream_inputs) + collected.update(tool.Name for tool in upstream_inputs) + + +class CollectUpstreamInputs(pyblish.api.InstancePlugin): + """Collect source input containers used for this publish. + + This will include `inputs` data of which loaded publishes were used in the + generation of this publish. This leaves an upstream trace to what was used + as input. + + """ + + label = "Collect Inputs" + order = pyblish.api.CollectorOrder + 0.2 + hosts = ["fusion"] + + def process(self, instance): + + # Get all upstream and include itself + tool = instance[0] + nodes = list(iter_upstream(tool)) + nodes.append(tool) + + # Collect containers for the given set of nodes + containers = collect_input_containers(nodes) + + inputs = [c["representation"] for c in containers] + instance.data["inputs"] = inputs + + self.log.info("Collected inputs: %s" % inputs) diff --git a/openpype/hosts/maya/api/lib_rendersetup.py b/openpype/hosts/maya/api/lib_rendersetup.py index 0fdc54a068..a00be52d8e 100644 --- a/openpype/hosts/maya/api/lib_rendersetup.py +++ b/openpype/hosts/maya/api/lib_rendersetup.py @@ -348,3 +348,71 @@ def get_attr_overrides(node_attr, layer, break return reversed(plug_overrides) + + +def get_shader_in_layer(node, layer): + """Return the assigned shader in a renderlayer without switching layers. + + This has been developed and tested for Legacy Renderlayers and *not* for + Render Setup. + + Note: This will also return the shader for any face assignments, however + it will *not* return the components they are assigned to. This could + be implemented, but since Maya's renderlayers are famous for breaking + with face assignments there has been no need for this function to + support that. + + Returns: + list: The list of assigned shaders in the given layer. + + """ + + def _get_connected_shader(shape): + """Return current shader""" + return cmds.listConnections(shape + ".instObjGroups", + source=False, + destination=True, + plugs=False, + connections=False, + type="shadingEngine") or [] + + # We check the instObjGroups (shader connection) for layer overrides. + plug = node + ".instObjGroups" + + # Ignore complex query if we're in the layer anyway (optimization) + current_layer = cmds.editRenderLayerGlobals(query=True, + currentRenderLayer=True) + if layer == current_layer: + return _get_connected_shader(plug) + + connections = cmds.listConnections(plug, + plugs=True, + source=False, + destination=True, + type="renderLayer") or [] + connections = filter(lambda x: x.endswith(".outPlug"), connections) + if not connections: + # If no overrides anywhere on the shader, just get the current shader + return _get_connected_shader(plug) + + def _get_override(connections, layer): + """Return the overridden connection for that layer in connections""" + # If there's an override on that layer, return that. + for connection in connections: + if (connection.startswith(layer + ".outAdjustments") and + connection.endswith(".outPlug")): + + # This is a shader override on that layer so get the shader + # connected to .outValue of the .outAdjustment[i] + out_adjustment = connection.rsplit(".", 1)[0] + connection_attr = out_adjustment + ".outValue" + override = cmds.listConnections(connection_attr) or [] + + return override + + override_shader = _get_override(connections, layer) + if override_shader is not None: + return override_shader + else: + # Get the override for "defaultRenderLayer" (=masterLayer) + return _get_override(connections, layer="defaultRenderLayer") diff --git a/openpype/hosts/maya/plugins/publish/collect_inputs.py b/openpype/hosts/maya/plugins/publish/collect_inputs.py new file mode 100644 index 0000000000..8afa1e4757 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_inputs.py @@ -0,0 +1,214 @@ +import copy +from maya import cmds +import maya.api.OpenMaya as om +import pyblish.api + +from openpype.pipeline import registered_host +from openpype.hosts.maya.api.lib import get_container_members +from openpype.hosts.maya.api.lib_rendersetup import get_shader_in_layer + + +def iter_history(nodes, + filter=om.MFn.kInvalid, + direction=om.MItDependencyGraph.kUpstream): + """Iterate unique upstream history for list of nodes. + + This acts as a replacement to maya.cmds.listHistory. + It's faster by about 2x-3x. It returns less than + maya.cmds.listHistory as it excludes the input nodes + from the output (unless an input node was history + for another input node). It also excludes duplicates. + + Args: + nodes (list): Maya node names to start search from. + filter (om.MFn.Type): Filter to only specific types. + e.g. to dag nodes using om.MFn.kDagNode + direction (om.MItDependencyGraph.Direction): Direction to traverse in. + Defaults to upstream. + + Yields: + str: Node names in upstream history. + + """ + if not nodes: + return + + sel = om.MSelectionList() + for node in nodes: + sel.add(node) + + it = om.MItDependencyGraph(sel.getDependNode(0)) # init iterator + handle = om.MObjectHandle + + traversed = set() + fn_dep = om.MFnDependencyNode() + fn_dag = om.MFnDagNode() + for i in range(sel.length()): + + start_node = sel.getDependNode(i) + start_node_hash = handle(start_node).hashCode() + if start_node_hash in traversed: + continue + + it.resetTo(start_node, + filter=filter, + direction=direction) + while not it.isDone(): + + node = it.currentNode() + node_hash = handle(node).hashCode() + + if node_hash in traversed: + it.prune() + it.next() + continue + + traversed.add(node_hash) + + if node.hasFn(om.MFn.kDagNode): + fn_dag.setObject(node) + yield fn_dag.fullPathName() + else: + fn_dep.setObject(node) + yield fn_dep.name() + + it.next() + + +def collect_input_containers(containers, nodes): + """Collect containers that contain any of the node in `nodes`. + + This will return any loaded Avalon container that contains at least one of + the nodes. As such, the Avalon container is an input for it. Or in short, + there are member nodes of that container. + + Returns: + list: Input avalon containers + + """ + # Assume the containers have collected their cached '_members' data + # in the collector. + return [container for container in containers + if any(node in container["_members"] for node in nodes)] + + +class CollectUpstreamInputs(pyblish.api.InstancePlugin): + """Collect input source inputs for this publish. + + This will include `inputs` data of which loaded publishes were used in the + generation of this publish. This leaves an upstream trace to what was used + as input. + + """ + + label = "Collect Inputs" + order = pyblish.api.CollectorOrder + 0.34 + hosts = ["maya"] + + def process(self, instance): + + # For large scenes the querying of "host.ls()" can be relatively slow + # e.g. up to a second. Many instances calling it easily slows this + # down. As such, we cache it so we trigger it only once. + # todo: Instead of hidden cache make "CollectContainers" plug-in + cache_key = "__cache_containers" + scene_containers = instance.context.data.get(cache_key, None) + if scene_containers is None: + # Query the scenes' containers if there's no cache yet + host = registered_host() + scene_containers = list(host.ls()) + for container in scene_containers: + # Embed the members into the container dictionary + container_members = set(get_container_members(container)) + container["_members"] = container_members + instance.context.data["__cache_containers"] = scene_containers + + # Collect the relevant input containers for this instance + if "renderlayer" in set(instance.data.get("families", [])): + # Special behavior for renderlayers + self.log.debug("Collecting renderlayer inputs....") + containers = self._collect_renderlayer_inputs(scene_containers, + instance) + + else: + # Basic behavior + nodes = instance[:] + + # Include any input connections of history with long names + # For optimization purposes only trace upstream from shape nodes + # looking for used dag nodes. This way having just a constraint + # on a transform is also ignored which tended to give irrelevant + # inputs for the majority of our use cases. We tend to care more + # about geometry inputs. + shapes = cmds.ls(nodes, + type=("mesh", "nurbsSurface", "nurbsCurve"), + noIntermediate=True) + if shapes: + history = list(iter_history(shapes, filter=om.MFn.kShape)) + history = cmds.ls(history, long=True) + + # Include the transforms in the collected history as shapes + # are excluded from containers + transforms = cmds.listRelatives(cmds.ls(history, shapes=True), + parent=True, + fullPath=True, + type="transform") + if transforms: + history.extend(transforms) + + if history: + nodes = list(set(nodes + history)) + + # Collect containers for the given set of nodes + containers = collect_input_containers(scene_containers, + nodes) + + inputs = [c["representation"] for c in containers] + instance.data["inputs"] = inputs + + self.log.info("Collected inputs: %s" % inputs) + + def _collect_renderlayer_inputs(self, scene_containers, instance): + """Collects inputs from nodes in renderlayer, incl. shaders + camera""" + + # Get the renderlayer + renderlayer = instance.data.get("setMembers") + + if renderlayer == "defaultRenderLayer": + # Assume all loaded containers in the scene are inputs + # for the masterlayer + return copy.deepcopy(scene_containers) + else: + # Get the members of the layer + members = cmds.editRenderLayerMembers(renderlayer, + query=True, + fullNames=True) or [] + + # In some cases invalid objects are returned from + # `editRenderLayerMembers` so we filter them out + members = cmds.ls(members, long=True) + + # Include all children + children = cmds.listRelatives(members, + allDescendents=True, + fullPath=True) or [] + members.extend(children) + + # Include assigned shaders in renderlayer + shapes = cmds.ls(members, shapes=True, long=True) + shaders = set() + for shape in shapes: + shape_shaders = get_shader_in_layer(shape, layer=renderlayer) + if not shape_shaders: + continue + shaders.update(shape_shaders) + members.extend(shaders) + + # Explicitly include the camera being rendered in renderlayer + cameras = instance.data.get("cameras") + members.extend(cameras) + + containers = collect_input_containers(scene_containers, members) + + return containers + From d92c6eac115d0e857738c5944abe11fc2c840f1e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Aug 2022 16:58:33 +0200 Subject: [PATCH 019/513] Remove blank line --- openpype/hosts/maya/plugins/publish/collect_inputs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_inputs.py b/openpype/hosts/maya/plugins/publish/collect_inputs.py index 8afa1e4757..d34f289e05 100644 --- a/openpype/hosts/maya/plugins/publish/collect_inputs.py +++ b/openpype/hosts/maya/plugins/publish/collect_inputs.py @@ -211,4 +211,3 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): containers = collect_input_containers(scene_containers, members) return containers - From 4721a683094acfc2fd709d44c83d3907d91a2aa2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Aug 2022 17:00:17 +0200 Subject: [PATCH 020/513] Shush the hound - code is correct --- openpype/hosts/maya/plugins/publish/collect_inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_inputs.py b/openpype/hosts/maya/plugins/publish/collect_inputs.py index d34f289e05..43941bde4f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_inputs.py +++ b/openpype/hosts/maya/plugins/publish/collect_inputs.py @@ -60,7 +60,7 @@ def iter_history(nodes, if node_hash in traversed: it.prune() - it.next() + it.next() # noqa: B305 continue traversed.add(node_hash) @@ -72,7 +72,7 @@ def iter_history(nodes, fn_dep.setObject(node) yield fn_dep.name() - it.next() + it.next() # noqa: B305 def collect_input_containers(containers, nodes): From 4a4bb22f60353bb9cfcdb64c516b2b145cb7c966 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Aug 2022 11:36:36 +0200 Subject: [PATCH 021/513] Refactor collect inputs as `inputRepresentations` --- openpype/hosts/fusion/plugins/publish/collect_inputs.py | 6 ++++-- openpype/hosts/houdini/plugins/publish/collect_inputs.py | 6 ++++-- openpype/hosts/maya/plugins/publish/collect_inputs.py | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_inputs.py b/openpype/hosts/fusion/plugins/publish/collect_inputs.py index e610575e3a..8f9857b02f 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_inputs.py +++ b/openpype/hosts/fusion/plugins/publish/collect_inputs.py @@ -1,3 +1,5 @@ +from bson.objectid import ObjectId + import pyblish.api from openpype.pipeline import registered_host @@ -106,7 +108,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): # Collect containers for the given set of nodes containers = collect_input_containers(nodes) - inputs = [c["representation"] for c in containers] - instance.data["inputs"] = inputs + inputs = [ObjectId(c["representation"]) for c in containers] + instance.data["inputRepresentations"] = inputs self.log.info("Collected inputs: %s" % inputs) diff --git a/openpype/hosts/houdini/plugins/publish/collect_inputs.py b/openpype/hosts/houdini/plugins/publish/collect_inputs.py index 8c7098c710..9ee0248bd9 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_inputs.py +++ b/openpype/hosts/houdini/plugins/publish/collect_inputs.py @@ -1,3 +1,5 @@ +from bson.objectid import ObjectId + import pyblish.api from openpype.pipeline import registered_host @@ -115,7 +117,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): # Collect containers for the given set of nodes containers = collect_input_containers(nodes) - inputs = [c["representation"] for c in containers] - instance.data["inputs"] = inputs + inputs = [ObjectId(c["representation"]) for c in containers] + instance.data["inputRepresentations"] = inputs self.log.info("Collected inputs: %s" % inputs) diff --git a/openpype/hosts/maya/plugins/publish/collect_inputs.py b/openpype/hosts/maya/plugins/publish/collect_inputs.py index 43941bde4f..470fceffc9 100644 --- a/openpype/hosts/maya/plugins/publish/collect_inputs.py +++ b/openpype/hosts/maya/plugins/publish/collect_inputs.py @@ -1,4 +1,6 @@ import copy +from bson.objectid import ObjectId + from maya import cmds import maya.api.OpenMaya as om import pyblish.api @@ -163,8 +165,8 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): containers = collect_input_containers(scene_containers, nodes) - inputs = [c["representation"] for c in containers] - instance.data["inputs"] = inputs + inputs = [ObjectId(c["representation"]) for c in containers] + instance.data["inputRepresentations"] = inputs self.log.info("Collected inputs: %s" % inputs) From 19f81dbf40bbe509506f9f13b4dcfa70133f8b92 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Aug 2022 11:38:44 +0200 Subject: [PATCH 022/513] Add Collector to convert `inputRepresentations` -> `inputVersions` --- ...llect_input_representations_to_versions.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 openpype/plugins/publish/collect_input_representations_to_versions.py diff --git a/openpype/plugins/publish/collect_input_representations_to_versions.py b/openpype/plugins/publish/collect_input_representations_to_versions.py new file mode 100644 index 0000000000..03f2abf51f --- /dev/null +++ b/openpype/plugins/publish/collect_input_representations_to_versions.py @@ -0,0 +1,48 @@ +import pyblish.api + +from bson.objectid import ObjectId + +from openpype.client import get_representations + + +class CollectInputRepresentationsToVersions(pyblish.api.ContextPlugin): + """Converts collected input representations to input versions. + + Any data in `instance.data["inputRepresentations"]` gets converted into + `instance.data["inputVersions"]` as supported in OpenPype v3. + + """ + # This is a ContextPlugin because then we can query the database only once + # for the conversion of representation ids to version ids (optimization) + label = "Input Representations to Versions" + order = pyblish.api.CollectorOrder + 0.499 + hosts = ["*"] + + def process(self, context): + # Query all version ids for representation ids from the database once + representations = set() + for instance in context: + inst_repre = instance.data.get("inputRepresentations", []) + representations.update(inst_repre) + + representations_docs = get_representations( + project_name=context.data["projectEntity"]["name"], + representation_ids=representations, + fields=["_id", "parent"]) + + representation_id_to_version_id = { + repre["_id"]: repre["parent"] for repre in representations_docs + } + + for instance in context: + inst_repre = instance.data.get("inputRepresentations", []) + if not inst_repre: + continue + + input_versions = instance.data.get("inputVersions", []) + for repre_id in inst_repre: + repre_id = ObjectId(repre_id) + version_id = representation_id_to_version_id[repre_id] + input_versions.append(version_id) + instance.data["inputVersions"] = input_versions + From 257f027d900e259d611bc70becaa1a30065ee3fd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Aug 2022 11:40:28 +0200 Subject: [PATCH 023/513] Remove blank line --- .../plugins/publish/collect_input_representations_to_versions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/collect_input_representations_to_versions.py b/openpype/plugins/publish/collect_input_representations_to_versions.py index 03f2abf51f..18a19bce80 100644 --- a/openpype/plugins/publish/collect_input_representations_to_versions.py +++ b/openpype/plugins/publish/collect_input_representations_to_versions.py @@ -45,4 +45,3 @@ class CollectInputRepresentationsToVersions(pyblish.api.ContextPlugin): version_id = representation_id_to_version_id[repre_id] input_versions.append(version_id) instance.data["inputVersions"] = input_versions - From 5b559fd28d439a2e9ba2185eae428b7c63b69fb5 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 9 Aug 2022 13:17:06 +0200 Subject: [PATCH 024/513] create shelf manager definition for houdini in openpype project settings --- .../defaults/project_settings/houdini.json | 21 +++++ .../schema_project_houdini.json | 6 +- .../schemas/schema_houdini_scriptshelf.json | 81 +++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 911bf82d9b..5805f600c5 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -1,4 +1,25 @@ { + "shelves": [ + { + "shelf_set_name": "OpenPype Shelves", + "shelf_set_source_path": "/path/to/your/shelf_set_file", + "shelf_definition": [ + { + "shelf_name": "OpenPype Shelf", + "shelf_file_path": "/path/to/your/shelf_file", + "tools_list": [ + { + "name": "OpenPype Tool", + "filepath": "/path/to/your/tool_file", + "script": "/path/to/your/tool_script", + "icon": "/path/to/your/icon", + "help": "Help message for your tool" + } + ] + } + ] + } + ], "create": { "CreateArnoldAss": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index cad99dde22..bde4352964 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -5,6 +5,10 @@ "label": "Houdini", "is_file": true, "children": [ + { + "type": "schema", + "name": "schema_houdini_scriptshelf" + }, { "type": "schema", "name": "schema_houdini_create" @@ -28,4 +32,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json new file mode 100644 index 0000000000..5a84c6d5cc --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json @@ -0,0 +1,81 @@ +{ + "type": "list", + "key": "shelves", + "label": "Shelves Manager", + "is_group": true, + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "shelf_set_name", + "label": "Shelf Set Name" + }, + { + "type": "path", + "key": "shelf_set_source_path", + "label": "Shelf Set Path", + "multipath": true, + "multiplatform": true + }, + { + "type": "list", + "key": "shelf_definition", + "label": "Shelves", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "shelf_name", + "label": "Shelf Name" + }, + { + "type": "text", + "key": "shelf_file_path", + "label": "Shelf File Path" + }, + { + "type": "list", + "key": "tools_list", + "label": "Tools", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "filepath", + "label": "File Path" + }, + { + "type": "text", + "key": "script", + "label": "Script" + }, + { + "type": "text", + "key": "icon", + "label": "Icon" + }, + { + "type": "text", + "key": "help", + "label": "Help" + } + ] + } + } + ] + } + } + ] + } +} \ No newline at end of file From a006b5df63bb0b3f3935f0873a2f4537966ffddb Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 9 Aug 2022 15:43:47 +0200 Subject: [PATCH 025/513] set up the shelf creation in the _set_context_settings function --- openpype/hosts/houdini/api/lib.py | 32 ++++++++++++++++++++++++++ openpype/hosts/houdini/api/pipeline.py | 2 ++ 2 files changed, 34 insertions(+) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index c8a7f92bb9..55832abeb3 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -460,3 +460,35 @@ def reset_framerange(): hou.playbar.setFrameRange(frame_start, frame_end) hou.playbar.setPlaybackRange(frame_start, frame_end) hou.setFrame(frame_start) + + +def create_shelf(): + hou.shelves.beginChangeBlock() + + custom_shelf = hou.shelves.newShelf( + file_path='', + name="custom_shelf", + label="Custom Shelf" + ) + + new_tool = hou.shelves.newTool( + file_path='', + name='new_tool', + label='New Tool', + script='', + language=hou.scriptLanguage.Python, + icon='', + help='This is a new tool' + ) + + if new_tool not in custom_shelf.tools(): + custom_shelf.setTools(list(custom_shelf.tools()) + [new_tool]) + + shelf_set = [ + shelf for shelf in hou.shelves.shelfSets().values() + if shelf.label() == "Create and Refine" + ][0] + + shelf_set.setShelves(shelf_set.shelves() + (custom_shelf,)) + + hou.shelves.endChangeBlock() diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index b5f5459392..2f414020c4 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -309,6 +309,7 @@ def _set_context_settings(): fps resolution renderer + shelves Returns: None @@ -320,6 +321,7 @@ def _set_context_settings(): lib.set_scene_fps(fps) lib.reset_framerange() + lib.create_shelf() def on_pyblish_instance_toggled(instance, new_value, old_value): From cdd90ad2a79de9ba0c2000a00eff65efcde30a8d Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 9 Aug 2022 17:13:08 +0200 Subject: [PATCH 026/513] main structure to generate shelves --- openpype/hosts/houdini/api/pipeline.py | 4 +-- openpype/hosts/houdini/api/shelves.py | 47 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/houdini/api/shelves.py diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 2f414020c4..f809f0ce56 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -14,7 +14,7 @@ from openpype.pipeline import ( ) from openpype.pipeline.load import any_outdated_containers import openpype.hosts.houdini -from openpype.hosts.houdini.api import lib +from openpype.hosts.houdini.api import lib, shelves from openpype.lib import ( register_event_callback, @@ -74,6 +74,7 @@ def install(): # so it initializes into the correct scene FPS, Frame Range, etc. # todo: make sure this doesn't trigger when opening with last workfile _set_context_settings() + shelves.generate_shelves() def uninstall(): @@ -321,7 +322,6 @@ def _set_context_settings(): lib.set_scene_fps(fps) lib.reset_framerange() - lib.create_shelf() def on_pyblish_instance_toggled(instance, new_value, old_value): diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py new file mode 100644 index 0000000000..b8f6419175 --- /dev/null +++ b/openpype/hosts/houdini/api/shelves.py @@ -0,0 +1,47 @@ +import os +import logging + +from openpype.settings import get_project_settings + +log = logging.getLogger(__name__) + + +def generate_shelves(): + # load configuration of custom menu + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + shelves_set_config = project_settings["houdini"]["shelves"] + + if not shelves_set_config: + log.warning("No custom shelves found.") + return + + # run the shelf generator for Houdini + for shelf_set in shelves_set_config: + pass + # if shelf_set_source_path is not None we load the source path and return + + # if the shelf set name already exists, do nothing, else, create a new one + + # go through each shelf + # if shelf_file_path exists, load the shelf and return + # if the shelf name already exists, do nothing, else, create a new one + + # go through each tool + # if filepath exists, load the tool, add it to the shelf and continue + # create the tool + # add it to a list of tools + + # add the tools list to the shelf with the tools already in it + # add the shelf to the shelf set with the shelfs already in it + + +def get_or_create_shelf_set(): + pass + + +def get_or_create_shelf(): + pass + + +def get_or_create_tool(): + pass From b74655c167aefd2d84e8cd6716d7b7b3c02783cd Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 11:56:05 +0200 Subject: [PATCH 027/513] set multipath to false for shelf set path --- .../settings/defaults/project_settings/houdini.json | 6 +++++- .../schemas/schema_houdini_scriptshelf.json | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 5805f600c5..2ceed37935 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -2,7 +2,11 @@ "shelves": [ { "shelf_set_name": "OpenPype Shelves", - "shelf_set_source_path": "/path/to/your/shelf_set_file", + "shelf_set_source_path": { + "windows": "", + "darwin": "", + "linux": "/path/to/your/shelf_set_file" + }, "shelf_definition": [ { "shelf_name": "OpenPype Shelf", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json index 5a84c6d5cc..ae05cef74e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json @@ -16,7 +16,7 @@ "type": "path", "key": "shelf_set_source_path", "label": "Shelf Set Path", - "multipath": true, + "multipath": false, "multiplatform": true }, { @@ -33,7 +33,7 @@ "label": "Shelf Name" }, { - "type": "text", + "type": "path", "key": "shelf_file_path", "label": "Shelf File Path" }, @@ -51,17 +51,17 @@ "label": "Name" }, { - "type": "text", + "type": "path", "key": "filepath", "label": "File Path" }, { - "type": "text", + "type": "path", "key": "script", "label": "Script" }, { - "type": "text", + "type": "path", "key": "icon", "label": "Icon" }, From a302caf6bd431b98136ce5b41c56cb0c60e49b4f Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 13:05:37 +0200 Subject: [PATCH 028/513] setting shelf set filepath if any in right OS --- openpype/hosts/houdini/api/shelves.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index b8f6419175..6ea4b4a9fd 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,26 +1,38 @@ import os import logging +import platform from openpype.settings import get_project_settings -log = logging.getLogger(__name__) +import hou + +log = logging.getLogger("openpype.hosts.houdini") def generate_shelves(): + current_os = platform.system().lower() # load configuration of custom menu project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) shelves_set_config = project_settings["houdini"]["shelves"] if not shelves_set_config: - log.warning("No custom shelves found.") + log.warning( + "SHELF ERROR: No custom shelves found in project settings." + ) return # run the shelf generator for Houdini - for shelf_set in shelves_set_config: - pass - # if shelf_set_source_path is not None we load the source path and return + for shelf_set_config in shelves_set_config: + shelf_set_filepath = shelf_set_config.get('shelf_set_source_path') + # if shelf_set_source_path is not None we load the source path and continue + if shelf_set_filepath[current_os]: + hou.shelves.newShelfSet(file_path=shelf_set_filepath[current_os]) + # hou.ShelfSet.setFilePath(file_path=shelf_set_filepath[operating_system]) + continue # if the shelf set name already exists, do nothing, else, create a new one + shelf_set_name = shelf_set_config.get('shelf_set_name') + shelf_set = get_or_create_shelf_set(shelf_set_name) # go through each shelf # if shelf_file_path exists, load the shelf and return @@ -35,8 +47,9 @@ def generate_shelves(): # add the shelf to the shelf set with the shelfs already in it -def get_or_create_shelf_set(): - pass +def get_or_create_shelf_set(shelf_set_name): + log.warning("IN GET OR CREATE SHELF SET: {}".format(shelf_set_name)) + hou.shelves.shelves() def get_or_create_shelf(): From 6f68b998965893401d1679913084942e7329b086 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 10 Aug 2022 15:47:32 +0200 Subject: [PATCH 029/513] Fix refactor typo --- openpype/hosts/maya/api/lib_rendersetup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib_rendersetup.py b/openpype/hosts/maya/api/lib_rendersetup.py index a00be52d8e..e616f26e1b 100644 --- a/openpype/hosts/maya/api/lib_rendersetup.py +++ b/openpype/hosts/maya/api/lib_rendersetup.py @@ -367,9 +367,9 @@ def get_shader_in_layer(node, layer): """ - def _get_connected_shader(shape): + def _get_connected_shader(plug): """Return current shader""" - return cmds.listConnections(shape + ".instObjGroups", + return cmds.listConnections(plug, source=False, destination=True, plugs=False, From b69e2e2003f768b111fa50635a0c5f3268ca7357 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 17:34:48 +0200 Subject: [PATCH 030/513] get shelf set or create one --- openpype/hosts/houdini/api/shelves.py | 29 ++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 6ea4b4a9fd..d89f3153ea 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -21,13 +21,19 @@ def generate_shelves(): ) return - # run the shelf generator for Houdini for shelf_set_config in shelves_set_config: shelf_set_filepath = shelf_set_config.get('shelf_set_source_path') + # if shelf_set_source_path is not None we load the source path and continue if shelf_set_filepath[current_os]: + if not os.path.isfile(shelf_set_filepath[current_os]): + raise FileNotFoundError( + "SHELF ERROR: This path doesn't exist - {}".format( + shelf_set_filepath[current_os] + ) + ) + hou.shelves.newShelfSet(file_path=shelf_set_filepath[current_os]) - # hou.ShelfSet.setFilePath(file_path=shelf_set_filepath[operating_system]) continue # if the shelf set name already exists, do nothing, else, create a new one @@ -47,9 +53,22 @@ def generate_shelves(): # add the shelf to the shelf set with the shelfs already in it -def get_or_create_shelf_set(shelf_set_name): - log.warning("IN GET OR CREATE SHELF SET: {}".format(shelf_set_name)) - hou.shelves.shelves() +def get_or_create_shelf_set(shelf_set_label): + all_shelves = hou.shelves.shelfSets().values() + + shelf_set = [ + shelf for shelf in all_shelves if shelf.label() == shelf_set_label + ] + + if shelf_set: + return shelf_set[0] + + shelf_set_name = shelf_set_label.replace(' ', '_').lower() + new_shelf_set = hou.shelves.newShelfSet( + name=shelf_set_name, + label=shelf_set_label + ) + return new_shelf_set def get_or_create_shelf(): From 937ba13ea0c62b63d2d56a0f1895932089070983 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 17:47:33 +0200 Subject: [PATCH 031/513] remove filepath for shelf and tools --- .../settings/defaults/project_settings/houdini.json | 2 -- .../schemas/schema_houdini_scriptshelf.json | 12 +----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 2ceed37935..a818f82d6b 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -10,11 +10,9 @@ "shelf_definition": [ { "shelf_name": "OpenPype Shelf", - "shelf_file_path": "/path/to/your/shelf_file", "tools_list": [ { "name": "OpenPype Tool", - "filepath": "/path/to/your/tool_file", "script": "/path/to/your/tool_script", "icon": "/path/to/your/icon", "help": "Help message for your tool" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json index ae05cef74e..812ab7d8c9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json @@ -15,7 +15,7 @@ { "type": "path", "key": "shelf_set_source_path", - "label": "Shelf Set Path", + "label": "Shelf Set Path (optional)", "multipath": false, "multiplatform": true }, @@ -32,11 +32,6 @@ "key": "shelf_name", "label": "Shelf Name" }, - { - "type": "path", - "key": "shelf_file_path", - "label": "Shelf File Path" - }, { "type": "list", "key": "tools_list", @@ -50,11 +45,6 @@ "key": "name", "label": "Name" }, - { - "type": "path", - "key": "filepath", - "label": "File Path" - }, { "type": "path", "key": "script", From ea37f4c3c5313e6c088e533c10c721b33d490333 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 18:06:38 +0200 Subject: [PATCH 032/513] get or create shelf implementation --- openpype/hosts/houdini/api/shelves.py | 52 +++++++++++++++++++++------ 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index d89f3153ea..76fe0cbd87 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,3 +1,4 @@ +from cProfile import label import os import logging import platform @@ -17,14 +18,13 @@ def generate_shelves(): if not shelves_set_config: log.warning( - "SHELF ERROR: No custom shelves found in project settings." + "SHELF WARNGING: No custom shelves found in project settings." ) return for shelf_set_config in shelves_set_config: shelf_set_filepath = shelf_set_config.get('shelf_set_source_path') - # if shelf_set_source_path is not None we load the source path and continue if shelf_set_filepath[current_os]: if not os.path.isfile(shelf_set_filepath[current_os]): raise FileNotFoundError( @@ -36,13 +36,33 @@ def generate_shelves(): hou.shelves.newShelfSet(file_path=shelf_set_filepath[current_os]) continue - # if the shelf set name already exists, do nothing, else, create a new one shelf_set_name = shelf_set_config.get('shelf_set_name') + if not shelf_set_name: + log.warning( + "SHELF WARNGING: No name found in shelf set definition." + ) + return + shelf_set = get_or_create_shelf_set(shelf_set_name) - # go through each shelf - # if shelf_file_path exists, load the shelf and return - # if the shelf name already exists, do nothing, else, create a new one + shelves_definition = shelf_set_config.get('shelf_definition') + + if not shelves_definition: + log.warning( + "SHELF WARNING: \ +No shelf definition found for shelf set named '{}'".format(shelf_set_name) + ) + return + + for shelf_definition in shelves_definition: + shelf_name = shelf_definition.get('shelf_name') + if not shelf_name: + log.warning( + "SHELF WARNGING: No name found in shelf set definition." + ) + return + + shelf = get_or_create_shelf(shelf_name) # go through each tool # if filepath exists, load the tool, add it to the shelf and continue @@ -54,10 +74,10 @@ def generate_shelves(): def get_or_create_shelf_set(shelf_set_label): - all_shelves = hou.shelves.shelfSets().values() + all_shelves_sets = hou.shelves.shelfSets().values() shelf_set = [ - shelf for shelf in all_shelves if shelf.label() == shelf_set_label + shelf for shelf in all_shelves_sets if shelf.label() == shelf_set_label ] if shelf_set: @@ -71,8 +91,20 @@ def get_or_create_shelf_set(shelf_set_label): return new_shelf_set -def get_or_create_shelf(): - pass +def get_or_create_shelf(shelf_label): + all_shelves = hou.shelves.shelves().values() + + shelf = [s for s in all_shelves if s.label() == shelf_label] + + if shelf: + return shelf[0] + + shelf_name = shelf_label.replace(' ', '_').lower() + new_shelf = hou.shelves.newShelf( + name=shelf_name, + label=shelf_label + ) + return new_shelf def get_or_create_tool(): From c4854be5c090cf63f2044fc81b8a7e33ee8c642d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 10 Aug 2022 18:48:54 +0200 Subject: [PATCH 033/513] OP-3682 - extracted sha256 method to lib --- openpype/client/addon_distribution.py | 149 ++++++++++++++++++++++++++ openpype/lib/path_tools.py | 20 ++++ openpype/tools/repack_version.py | 24 +---- 3 files changed, 172 insertions(+), 21 deletions(-) create mode 100644 openpype/client/addon_distribution.py diff --git a/openpype/client/addon_distribution.py b/openpype/client/addon_distribution.py new file mode 100644 index 0000000000..3246c5bb72 --- /dev/null +++ b/openpype/client/addon_distribution.py @@ -0,0 +1,149 @@ +import os +from enum import Enum +from zipfile import ZipFile +from abc import abstractmethod + +import attr + +from openpype.lib.path_tools import sha256sum +from openpype.lib import PypeLogger + +log = PypeLogger().get_logger(__name__) + + +class UrlType(Enum): + HTTP = {} + GIT = {} + OS = {} + + +@attr.s +class AddonInfo(object): + """Object matching json payload from Server""" + name = attr.ib(default=None) + version = attr.ib(default=None) + addon_url = attr.ib(default=None) + type = attr.ib(default=None) + hash = attr.ib(default=None) + + +class AddonDownloader: + + def __init__(self): + self._downloaders = {} + + def register_format(self, downloader_type, downloader): + self._downloaders[downloader_type] = downloader + + def get_downloader(self, downloader_type): + downloader = self._downloaders.get(downloader_type) + if not downloader: + raise ValueError(f"{downloader_type} not implemented") + return downloader() + + @classmethod + @abstractmethod + def download(cls, addon_url, destination): + """Returns url to downloaded addon zip file. + + Args: + addon_url (str): http or OS or any supported protocol url to addon + zip file + destination (str): local folder to unzip + Retursn: + (str) local path to addon zip file + """ + pass + + @classmethod + def check_hash(cls, addon_path, addon_hash): + """Compares 'hash' of downloaded 'addon_url' file. + + Args: + addon_path (str): local path to addon zip file + addon_hash (str): sha256 hash of zip file + Raises: + ValueError if hashes doesn't match + """ + if addon_hash != sha256sum(addon_path): + raise ValueError( + "{} doesn't match expected hash".format(addon_path)) + + @classmethod + def unzip(cls, addon_path, destination): + """Unzips local 'addon_path' to 'destination'. + + Args: + addon_path (str): local path to addon zip file + destination (str): local folder to unzip + """ + addon_file_name = os.path.basename(addon_path) + addon_base_file_name, _ = os.path.splitext(addon_file_name) + with ZipFile(addon_path, "r") as zip_ref: + log.debug(f"Unzipping {addon_path} to {destination}.") + zip_ref.extractall( + os.path.join(destination, addon_base_file_name)) + + @classmethod + def remove(cls, addon_url): + pass + + +class OSAddonDownloader(AddonDownloader): + + @classmethod + def download(cls, addon_url, destination): + # OS doesnt need to download, unzip directly + if not os.path.exists(addon_url): + raise ValueError("{} is not accessible".format(addon_url)) + return addon_url + + +def get_addons_info(): + """Returns list of addon information from Server""" + # TODO temp + addon_info = AddonInfo( + **{"name": "openpype_slack", + "version": "1.0.0", + "addon_url": "c:/projects/openpype_slack_1.0.0.zip", + "type": UrlType.OS, + "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658"}) # noqa + + return [addon_info] + + +def update_addon_state(addon_infos, destination_folder, factory): + """Loops through all 'addon_infos', compares local version, unzips. + + Loops through server provided list of dictionaries with information about + available addons. Looks if each addon is already present and deployed. + If isn't, addon zip gets downloaded and unzipped into 'destination_folder'. + Args: + addon_infos (list of AddonInfo) + destination_folder (str): local path + factory (AddonDownloader): factory to get appropriate downloader per + addon type + """ + for addon in addon_infos: + full_name = "{}_{}".format(addon.name, addon.version) + addon_url = os.path.join(destination_folder, full_name) + + if os.path.isdir(addon_url): + log.debug(f"Addon version folder {addon_url} already exists.") + continue + + downloader = factory.get_downloader(addon.type) + downloader.download(addon.addon_url, destination_folder) + + +def cli(args): + addon_folder = "c:/Users/petrk/AppData/Local/pypeclub/openpype/addons" + + downloader_factory = AddonDownloader() + downloader_factory.register_format(UrlType.OS, OSAddonDownloader) + + print(update_addon_state(get_addons_info(), addon_folder, + downloader_factory)) + print(sha256sum("c:/projects/openpype_slack_1.0.0.zip")) + + diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 4f28be3302..2083dc48d1 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -5,6 +5,7 @@ import json import logging import six import platform +import hashlib from openpype.client import get_project from openpype.settings import get_project_settings @@ -478,3 +479,22 @@ class HostDirmap: log.debug("local sync mapping:: {}".format(mapping)) return mapping + + +def sha256sum(filename): + """Calculate sha256 for content of the file. + + Args: + filename (str): Path to file. + + Returns: + str: hex encoded sha256 + + """ + h = hashlib.sha256() + b = bytearray(128 * 1024) + mv = memoryview(b) + with open(filename, 'rb', buffering=0) as f: + for n in iter(lambda: f.readinto(mv), 0): + h.update(mv[:n]) + return h.hexdigest() \ No newline at end of file diff --git a/openpype/tools/repack_version.py b/openpype/tools/repack_version.py index 0172264c79..414152970a 100644 --- a/openpype/tools/repack_version.py +++ b/openpype/tools/repack_version.py @@ -7,10 +7,11 @@ from pathlib import Path import platform from zipfile import ZipFile from typing import List -import hashlib import sys from igniter.bootstrap_repos import OpenPypeVersion +from openpype.lib.path_tools import sha256sum + class VersionRepacker: @@ -45,25 +46,6 @@ class VersionRepacker: print("{}{}".format(header, msg)) - @staticmethod - def sha256sum(filename): - """Calculate sha256 for content of the file. - - Args: - filename (str): Path to file. - - Returns: - str: hex encoded sha256 - - """ - h = hashlib.sha256() - b = bytearray(128 * 1024) - mv = memoryview(b) - with open(filename, 'rb', buffering=0) as f: - for n in iter(lambda: f.readinto(mv), 0): - h.update(mv[:n]) - return h.hexdigest() - @staticmethod def _filter_dir(path: Path, path_filter: List) -> List[Path]: """Recursively crawl over path and filter.""" @@ -104,7 +86,7 @@ class VersionRepacker: nits="%", color="green") for file in file_list: checksums.append(( - VersionRepacker.sha256sum(file.as_posix()), + sha256sum(file.as_posix()), file.resolve().relative_to(self.version_path), file )) From 66b280796e30ad89bfc5ef2e43f3f1b677d64a4f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 10 Aug 2022 18:49:24 +0200 Subject: [PATCH 034/513] OP-3682 - implemented local disk downloader --- openpype/client/addon_distribution.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/client/addon_distribution.py b/openpype/client/addon_distribution.py index 3246c5bb72..8fe9567688 100644 --- a/openpype/client/addon_distribution.py +++ b/openpype/client/addon_distribution.py @@ -144,6 +144,5 @@ def cli(args): print(update_addon_state(get_addons_info(), addon_folder, downloader_factory)) - print(sha256sum("c:/projects/openpype_slack_1.0.0.zip")) From 159052f8f9555ec1706d2c565b74133c785096ec Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 11:24:41 +0200 Subject: [PATCH 035/513] OP-3682 - Hound --- openpype/lib/path_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 2083dc48d1..0ae5e44d79 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -497,4 +497,4 @@ def sha256sum(filename): with open(filename, 'rb', buffering=0) as f: for n in iter(lambda: f.readinto(mv), 0): h.update(mv[:n]) - return h.hexdigest() \ No newline at end of file + return h.hexdigest() From a6ddb2d44b9ec9edb76c2a41f1b471909afabde6 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 11:44:42 +0200 Subject: [PATCH 036/513] filter mandatory attributes for tool --- openpype/hosts/houdini/api/shelves.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 76fe0cbd87..a37ec88d64 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -64,8 +64,17 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf = get_or_create_shelf(shelf_name) - # go through each tool - # if filepath exists, load the tool, add it to the shelf and continue + tools = [] + for tool in shelf_definition.get('tools_list'): + mandatory_attributes = ['name', 'script'] + if not all( + [v for k, v in tool.items() if k in mandatory_attributes] + ): + log.warning("TOOLS ERROR: You need to specify at least \ +the name and the script path of the tool.") + return + + tool = get_or_create_tool(tool, shelf) # create the tool # add it to a list of tools @@ -107,5 +116,5 @@ def get_or_create_shelf(shelf_label): return new_shelf -def get_or_create_tool(): +def get_or_create_tool(tool_definition, shelf): pass From 27125a1088786f004404302485b750fa1594462d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 12:02:55 +0200 Subject: [PATCH 037/513] OP-3682 - extract file_handler from tests Addon distribution could use already implemented methods for dowloading from HTTP (GDrive urls). --- {tests => openpype}/lib/file_handler.py | 0 tests/lib/testing_classes.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {tests => openpype}/lib/file_handler.py (100%) diff --git a/tests/lib/file_handler.py b/openpype/lib/file_handler.py similarity index 100% rename from tests/lib/file_handler.py rename to openpype/lib/file_handler.py diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 2b4d7deb48..75f859de48 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -10,7 +10,7 @@ import glob import platform from tests.lib.db_handler import DBHandler -from tests.lib.file_handler import RemoteFileHandler +from openpype.lib.file_handler import RemoteFileHandler from openpype.lib.remote_publish import find_variant_key From 66899d9dd9b50c6bd9285d191fd1da116ab03f4f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 13:04:13 +0200 Subject: [PATCH 038/513] OP-3682 - implemented download from HTTP Handles shared links from GDrive. --- openpype/client/addon_distribution.py | 59 ++++++++++++++++++--------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/openpype/client/addon_distribution.py b/openpype/client/addon_distribution.py index 8fe9567688..de84c7301a 100644 --- a/openpype/client/addon_distribution.py +++ b/openpype/client/addon_distribution.py @@ -7,14 +7,15 @@ import attr from openpype.lib.path_tools import sha256sum from openpype.lib import PypeLogger +from openpype.lib.file_handler import RemoteFileHandler log = PypeLogger().get_logger(__name__) class UrlType(Enum): - HTTP = {} - GIT = {} - OS = {} + HTTP = "http" + GIT = "git" + OS = "os" @attr.s @@ -70,19 +71,15 @@ class AddonDownloader: "{} doesn't match expected hash".format(addon_path)) @classmethod - def unzip(cls, addon_path, destination): - """Unzips local 'addon_path' to 'destination'. + def unzip(cls, addon_zip_path, destination): + """Unzips local 'addon_zip_path' to 'destination'. Args: - addon_path (str): local path to addon zip file + addon_zip_path (str): local path to addon zip file destination (str): local folder to unzip """ - addon_file_name = os.path.basename(addon_path) - addon_base_file_name, _ = os.path.splitext(addon_file_name) - with ZipFile(addon_path, "r") as zip_ref: - log.debug(f"Unzipping {addon_path} to {destination}.") - zip_ref.extractall( - os.path.join(destination, addon_base_file_name)) + RemoteFileHandler.unzip(addon_zip_path, destination) + os.remove(addon_zip_path) @classmethod def remove(cls, addon_url): @@ -99,6 +96,23 @@ class OSAddonDownloader(AddonDownloader): return addon_url +class HTTPAddonDownloader(AddonDownloader): + CHUNK_SIZE = 100000 + + @classmethod + def download(cls, addon_url, destination): + log.debug(f"Downloading {addon_url} to {destination}") + file_name = os.path.basename(destination) + _, ext = os.path.splitext(file_name) + if (ext.replace(".", '') not + in set(RemoteFileHandler.IMPLEMENTED_ZIP_FORMATS)): + file_name += ".zip" + RemoteFileHandler.download_url(addon_url, + destination, + filename=file_name) + + return os.path.join(destination, file_name) + def get_addons_info(): """Returns list of addon information from Server""" # TODO temp @@ -109,7 +123,14 @@ def get_addons_info(): "type": UrlType.OS, "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658"}) # noqa - return [addon_info] + http_addon = AddonInfo( + **{"name": "openpype_slack", + "version": "1.0.0", + "addon_url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing", # noqa + "type": UrlType.HTTP, + "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658"}) # noqa + + return [http_addon] def update_addon_state(addon_infos, destination_folder, factory): @@ -126,14 +147,15 @@ def update_addon_state(addon_infos, destination_folder, factory): """ for addon in addon_infos: full_name = "{}_{}".format(addon.name, addon.version) - addon_url = os.path.join(destination_folder, full_name) + addon_dest = os.path.join(destination_folder, full_name) - if os.path.isdir(addon_url): - log.debug(f"Addon version folder {addon_url} already exists.") + if os.path.isdir(addon_dest): + log.debug(f"Addon version folder {addon_dest} already exists.") continue downloader = factory.get_downloader(addon.type) - downloader.download(addon.addon_url, destination_folder) + zip_file_path = downloader.download(addon.addon_url, addon_dest) + downloader.unzip(zip_file_path, addon_dest) def cli(args): @@ -141,8 +163,7 @@ def cli(args): downloader_factory = AddonDownloader() downloader_factory.register_format(UrlType.OS, OSAddonDownloader) + downloader_factory.register_format(UrlType.HTTP, HTTPAddonDownloader) print(update_addon_state(get_addons_info(), addon_folder, downloader_factory)) - - From eab14fc5e9204126554e8be5898010e0db0398ca Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Aug 2022 13:20:27 +0200 Subject: [PATCH 039/513] Include inputVersions with the publish job instance metadata --- .../modules/deadline/plugins/publish/submit_publish_job.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index f05ef31938..2fa7da5dac 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -774,7 +774,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview", True) + "useSequenceForReview": data.get("useSequenceForReview", True), + # map inputVersions `ObjectId` -> `str` so json supports it + "inputVersions": list(map(str, data.get("inputVersions", []))) } # skip locking version if we are creating v01 From bc33432a57bf16245f5bbc9ca22f1f26fbea9dd1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 16:05:56 +0200 Subject: [PATCH 040/513] OP-3682 - updated hash logic Currently only checking hash of zip file. --- openpype/client/addon_distribution.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/client/addon_distribution.py b/openpype/client/addon_distribution.py index de84c7301a..95c8e7d23f 100644 --- a/openpype/client/addon_distribution.py +++ b/openpype/client/addon_distribution.py @@ -1,6 +1,5 @@ import os from enum import Enum -from zipfile import ZipFile from abc import abstractmethod import attr @@ -66,9 +65,10 @@ class AddonDownloader: Raises: ValueError if hashes doesn't match """ + if not os.path.exists(addon_path): + raise ValueError(f"{addon_path} doesn't exist.") if addon_hash != sha256sum(addon_path): - raise ValueError( - "{} doesn't match expected hash".format(addon_path)) + raise ValueError(f"{addon_path} doesn't match expected hash.") @classmethod def unzip(cls, addon_zip_path, destination): @@ -153,9 +153,14 @@ def update_addon_state(addon_infos, destination_folder, factory): log.debug(f"Addon version folder {addon_dest} already exists.") continue - downloader = factory.get_downloader(addon.type) - zip_file_path = downloader.download(addon.addon_url, addon_dest) - downloader.unzip(zip_file_path, addon_dest) + try: + downloader = factory.get_downloader(addon.type) + zip_file_path = downloader.download(addon.addon_url, addon_dest) + downloader.check_hash(zip_file_path, addon.hash) + downloader.unzip(zip_file_path, addon_dest) + except Exception: + log.warning(f"Error happened during updating {addon.name}", + stack_info=True) def cli(args): From 161ae6ef77f0ac0f2017e7b64fdd50331c03592d Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 14:59:52 +0200 Subject: [PATCH 041/513] change key 'name' by 'label' for tool name --- openpype/settings/defaults/project_settings/houdini.json | 2 +- .../projects_schema/schemas/schema_houdini_scriptshelf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index a818f82d6b..78e0d595cf 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -12,7 +12,7 @@ "shelf_name": "OpenPype Shelf", "tools_list": [ { - "name": "OpenPype Tool", + "label": "OpenPype Tool", "script": "/path/to/your/tool_script", "icon": "/path/to/your/icon", "help": "Help message for your tool" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json index 812ab7d8c9..bab9b604b4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json @@ -42,7 +42,7 @@ "children": [ { "type": "text", - "key": "name", + "key": "label", "label": "Name" }, { From 778140b388c57ef8af0c4f69250cebf673dd6e74 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 16:21:23 +0200 Subject: [PATCH 042/513] add tool creation and adding tool to shelf and shelf to shelf_set --- openpype/hosts/houdini/api/shelves.py | 49 +++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index a37ec88d64..0687e2f519 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,4 +1,3 @@ -from cProfile import label import os import logging import platform @@ -64,22 +63,23 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf = get_or_create_shelf(shelf_name) - tools = [] - for tool in shelf_definition.get('tools_list'): + for tool_definition in shelf_definition.get('tools_list'): mandatory_attributes = ['name', 'script'] if not all( - [v for k, v in tool.items() if k in mandatory_attributes] + [v for k, v in tool_definition.items() if + k in mandatory_attributes] ): log.warning("TOOLS ERROR: You need to specify at least \ the name and the script path of the tool.") return - tool = get_or_create_tool(tool, shelf) - # create the tool - # add it to a list of tools + tool = get_or_create_tool(tool_definition, shelf) - # add the tools list to the shelf with the tools already in it - # add the shelf to the shelf set with the shelfs already in it + if tool not in shelf.tools(): + shelf.setTools(list(shelf.tools()) + [tool]) + + if shelf not in shelf_set.shelves(): + shelf_set.setShelves(shelf_set.shelves() + (shelf,)) def get_or_create_shelf_set(shelf_set_label): @@ -117,4 +117,33 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - pass + existing_tools = shelf.tools() + tool_label = tool_definition.get('label') + + existing_tool = [ + tool for tool in existing_tools if tool.label() == tool_label + ] + + if existing_tool: + tool_definition.pop('name', None) + tool_definition.pop('label', None) + existing_tool[0].setData(**tool_definition) + return existing_tool[0] + + tool_name = tool_label.replace(' ', '_').lower() + + if not os.path.exists(tool_definition['script']): + log.warning( + "TOOL ERROR: This path doesn't exist - {}".format( + tool_definition['script'] + ) + ) + return + + with open(tool_definition['script']) as f: + script = f.read() + tool_definition.update({'script': script}) + + new_tool = hou.shelves.newTool(name=tool_name, **tool_definition) + + return new_tool From ceeb652699a4dd5a2ceb2ccba15ae84f57684e07 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 16:36:55 +0200 Subject: [PATCH 043/513] OP-3682 - changed logging method PypeLogger is obsolete --- openpype/client/addon_distribution.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/client/addon_distribution.py b/openpype/client/addon_distribution.py index 95c8e7d23f..46098cfa11 100644 --- a/openpype/client/addon_distribution.py +++ b/openpype/client/addon_distribution.py @@ -1,14 +1,11 @@ import os from enum import Enum from abc import abstractmethod - import attr from openpype.lib.path_tools import sha256sum -from openpype.lib import PypeLogger from openpype.lib.file_handler import RemoteFileHandler - -log = PypeLogger().get_logger(__name__) +from openpype.lib import Logger class UrlType(Enum): @@ -28,6 +25,7 @@ class AddonInfo(object): class AddonDownloader: + log = Logger.get_logger(__name__) def __init__(self): self._downloaders = {} @@ -101,7 +99,7 @@ class HTTPAddonDownloader(AddonDownloader): @classmethod def download(cls, addon_url, destination): - log.debug(f"Downloading {addon_url} to {destination}") + cls.log.debug(f"Downloading {addon_url} to {destination}") file_name = os.path.basename(destination) _, ext = os.path.splitext(file_name) if (ext.replace(".", '') not @@ -113,6 +111,7 @@ class HTTPAddonDownloader(AddonDownloader): return os.path.join(destination, file_name) + def get_addons_info(): """Returns list of addon information from Server""" # TODO temp @@ -145,6 +144,10 @@ def update_addon_state(addon_infos, destination_folder, factory): factory (AddonDownloader): factory to get appropriate downloader per addon type """ + from openpype.lib import Logger + + log = Logger.get_logger(__name__) + for addon in addon_infos: full_name = "{}_{}".format(addon.name, addon.version) addon_dest = os.path.join(destination_folder, full_name) From 542eedb4b299aeab0a6e74a361e72e3961c17bfb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 17:19:58 +0200 Subject: [PATCH 044/513] OP-3682 - moved file to distribution folder Needs to be separate from Openpype. Igniter and Openpype (and tests) could import from this if necessary. --- {openpype/client => distribution}/addon_distribution.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {openpype/client => distribution}/addon_distribution.py (100%) diff --git a/openpype/client/addon_distribution.py b/distribution/addon_distribution.py similarity index 100% rename from openpype/client/addon_distribution.py rename to distribution/addon_distribution.py From cfbc9b00777073b945b1ec25e18c32b89127ed7c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 17:24:38 +0200 Subject: [PATCH 045/513] OP-3682 - replaced Logger to logging Shouldn't import anything from Openpype --- distribution/addon_distribution.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index 46098cfa11..b76cd8e3f8 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -2,10 +2,10 @@ import os from enum import Enum from abc import abstractmethod import attr +import logging from openpype.lib.path_tools import sha256sum from openpype.lib.file_handler import RemoteFileHandler -from openpype.lib import Logger class UrlType(Enum): @@ -25,7 +25,7 @@ class AddonInfo(object): class AddonDownloader: - log = Logger.get_logger(__name__) + log = logging.getLogger(__name__) def __init__(self): self._downloaders = {} @@ -132,7 +132,8 @@ def get_addons_info(): return [http_addon] -def update_addon_state(addon_infos, destination_folder, factory): +def update_addon_state(addon_infos, destination_folder, factory, + log=None): """Loops through all 'addon_infos', compares local version, unzips. Loops through server provided list of dictionaries with information about @@ -143,10 +144,10 @@ def update_addon_state(addon_infos, destination_folder, factory): destination_folder (str): local path factory (AddonDownloader): factory to get appropriate downloader per addon type + log (logging.Logger) """ - from openpype.lib import Logger - - log = Logger.get_logger(__name__) + if not log: + log = logging.getLogger(__name__) for addon in addon_infos: full_name = "{}_{}".format(addon.name, addon.version) From 98444762cd97da52e62370677762a82b25b850c8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 17:27:14 +0200 Subject: [PATCH 046/513] OP-3682 - moved file_handler --- distribution/addon_distribution.py | 5 ++--- {openpype/lib => distribution}/file_handler.py | 2 +- tests/lib/testing_classes.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) rename {openpype/lib => distribution}/file_handler.py (99%) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index b76cd8e3f8..e29e9bbf9b 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -4,8 +4,7 @@ from abc import abstractmethod import attr import logging -from openpype.lib.path_tools import sha256sum -from openpype.lib.file_handler import RemoteFileHandler +from distribution.file_handler import RemoteFileHandler class UrlType(Enum): @@ -65,7 +64,7 @@ class AddonDownloader: """ if not os.path.exists(addon_path): raise ValueError(f"{addon_path} doesn't exist.") - if addon_hash != sha256sum(addon_path): + if addon_hash != RemoteFileHandler.calculate_md5(addon_path): raise ValueError(f"{addon_path} doesn't match expected hash.") @classmethod diff --git a/openpype/lib/file_handler.py b/distribution/file_handler.py similarity index 99% rename from openpype/lib/file_handler.py rename to distribution/file_handler.py index ee3abc6ecb..8c8b4230ce 100644 --- a/openpype/lib/file_handler.py +++ b/distribution/file_handler.py @@ -21,7 +21,7 @@ class RemoteFileHandler: 'tar.gz', 'tar.xz', 'tar.bz2'] @staticmethod - def calculate_md5(fpath, chunk_size): + def calculate_md5(fpath, chunk_size=10000): md5 = hashlib.md5() with open(fpath, 'rb') as f: for chunk in iter(lambda: f.read(chunk_size), b''): diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 75f859de48..e819ae80de 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -10,7 +10,7 @@ import glob import platform from tests.lib.db_handler import DBHandler -from openpype.lib.file_handler import RemoteFileHandler +from distribution.file_handler import RemoteFileHandler from openpype.lib.remote_publish import find_variant_key From b0c8a47f0f27a734f8ba9f201ae08dabe5d1271d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 17:32:22 +0200 Subject: [PATCH 047/513] Revert "OP-3682 - extracted sha256 method to lib" This reverts commit c4854be5 --- openpype/lib/path_tools.py | 19 ------------------- openpype/tools/repack_version.py | 24 +++++++++++++++++++++--- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 0ae5e44d79..11648f9969 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -5,7 +5,6 @@ import json import logging import six import platform -import hashlib from openpype.client import get_project from openpype.settings import get_project_settings @@ -480,21 +479,3 @@ class HostDirmap: log.debug("local sync mapping:: {}".format(mapping)) return mapping - -def sha256sum(filename): - """Calculate sha256 for content of the file. - - Args: - filename (str): Path to file. - - Returns: - str: hex encoded sha256 - - """ - h = hashlib.sha256() - b = bytearray(128 * 1024) - mv = memoryview(b) - with open(filename, 'rb', buffering=0) as f: - for n in iter(lambda: f.readinto(mv), 0): - h.update(mv[:n]) - return h.hexdigest() diff --git a/openpype/tools/repack_version.py b/openpype/tools/repack_version.py index 414152970a..0172264c79 100644 --- a/openpype/tools/repack_version.py +++ b/openpype/tools/repack_version.py @@ -7,11 +7,10 @@ from pathlib import Path import platform from zipfile import ZipFile from typing import List +import hashlib import sys from igniter.bootstrap_repos import OpenPypeVersion -from openpype.lib.path_tools import sha256sum - class VersionRepacker: @@ -46,6 +45,25 @@ class VersionRepacker: print("{}{}".format(header, msg)) + @staticmethod + def sha256sum(filename): + """Calculate sha256 for content of the file. + + Args: + filename (str): Path to file. + + Returns: + str: hex encoded sha256 + + """ + h = hashlib.sha256() + b = bytearray(128 * 1024) + mv = memoryview(b) + with open(filename, 'rb', buffering=0) as f: + for n in iter(lambda: f.readinto(mv), 0): + h.update(mv[:n]) + return h.hexdigest() + @staticmethod def _filter_dir(path: Path, path_filter: List) -> List[Path]: """Recursively crawl over path and filter.""" @@ -86,7 +104,7 @@ class VersionRepacker: nits="%", color="green") for file in file_list: checksums.append(( - sha256sum(file.as_posix()), + VersionRepacker.sha256sum(file.as_posix()), file.resolve().relative_to(self.version_path), file )) From 0fb8988522a328afa33cc08960a8a2a678e2b26c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Aug 2022 17:35:12 +0200 Subject: [PATCH 048/513] OP-3682 - Hound --- openpype/lib/path_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 11648f9969..4f28be3302 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -478,4 +478,3 @@ class HostDirmap: log.debug("local sync mapping:: {}".format(mapping)) return mapping - From 532432d81739b2996ae94e56b6bf2faf36498dc3 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 17:57:38 +0200 Subject: [PATCH 049/513] add docstrings --- openpype/hosts/houdini/api/shelves.py | 43 +++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 0687e2f519..bb92aa828e 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -10,8 +10,15 @@ log = logging.getLogger("openpype.hosts.houdini") def generate_shelves(): + """This function generates complete shelves from shef set to tools + in Houdini from openpype project settings houdini shelf definition. + + Raises: + FileNotFoundError: Raised when the shelf set filepath does not exist + """ current_os = platform.system().lower() - # load configuration of custom menu + + # load configuration of houdini shelves project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) shelves_set_config = project_settings["houdini"]["shelves"] @@ -57,13 +64,15 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf_name = shelf_definition.get('shelf_name') if not shelf_name: log.warning( - "SHELF WARNGING: No name found in shelf set definition." + "SHELF WARNGING: No name found in shelf definition." ) return shelf = get_or_create_shelf(shelf_name) for tool_definition in shelf_definition.get('tools_list'): + # We verify that the name and script attibutes of the tool + # are set mandatory_attributes = ['name', 'script'] if not all( [v for k, v in tool_definition.items() if @@ -75,14 +84,25 @@ the name and the script path of the tool.") tool = get_or_create_tool(tool_definition, shelf) + # Add the tool to the shelf if not already in it if tool not in shelf.tools(): shelf.setTools(list(shelf.tools()) + [tool]) + # Add the shelf in the shelf set if not already in it if shelf not in shelf_set.shelves(): shelf_set.setShelves(shelf_set.shelves() + (shelf,)) def get_or_create_shelf_set(shelf_set_label): + """This function verifies if the shelf set label exists. If not, + creates a new shelf set. + + Arguments: + shelf_set_label {str} -- The label of the shelf set + + Returns: + hou.ShelfSet -- The shelf set existing or the new one + """ all_shelves_sets = hou.shelves.shelfSets().values() shelf_set = [ @@ -101,6 +121,15 @@ def get_or_create_shelf_set(shelf_set_label): def get_or_create_shelf(shelf_label): + """This function verifies if the shelf label exists. If not, creates + a new shelf. + + Arguments: + shelf_label {str} -- The label of the shelf + + Returns: + hou.Shelf -- The shelf existing or the new one + """ all_shelves = hou.shelves.shelves().values() shelf = [s for s in all_shelves if s.label() == shelf_label] @@ -117,6 +146,16 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): + """This function verifies if the tool exsist and update it. If not, creates + a new one. + + Arguments: + tool_definition {dict} -- Dict with label, script, icon and help + shelf {hou.Shelf} -- The parent shelf of the tool + + Returns: + hou.Tool -- The tool updated or the new one + """ existing_tools = shelf.tools() tool_label = tool_definition.get('label') From 74161e931e17a736c3dae6ae24678f9db4d497d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Aug 2022 18:02:47 +0200 Subject: [PATCH 050/513] Show whether a subset is loaded into the current scene --- openpype/tools/loader/model.py | 157 +++++++++++++++++++------------ openpype/tools/loader/widgets.py | 3 +- 2 files changed, 101 insertions(+), 59 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index a5174bd804..3130f879df 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -14,9 +14,11 @@ from openpype.client import ( get_versions, get_hero_versions, get_version_by_name, - get_representations + get_representations, + get_representations_parents ) from openpype.pipeline import ( + registered_host, HeroVersionType, schema, ) @@ -136,7 +138,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "duration", "handles", "step", - "repre_info" + "repre_info", + "loaded_in_scene" ] column_labels_mapping = { @@ -150,7 +153,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "duration": "Duration", "handles": "Handles", "step": "Step", - "repre_info": "Availability" + "repre_info": "Availability", + "loaded_in_scene": "In scene" } SortAscendingRole = QtCore.Qt.UserRole + 2 @@ -231,8 +235,14 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._doc_fetching_stop = False self._doc_payload = {} - self.doc_fetched.connect(self._on_doc_fetched) + self._host = registered_host() + self._loaded_representation_ids = set() + # Refresh loaded scene containers only every 3 seconds at most + self._host_loaded_refresh_timeout = 3 + self._host_loaded_refresh_time = 0 + + self.doc_fetched.connect(self._on_doc_fetched) self.refresh() def get_item_by_id(self, item_id): @@ -472,6 +482,29 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version + # Check loaded subsets + subsets_loaded_by_id = set() + ids = self._loaded_representation_ids + if ids: + if self._doc_fetching_stop: + return + + # Get subsets from representations + # todo: optimize with aggregation query to distinct subset id + representations = get_representations(project_name, + representation_ids=ids, + fields=["parent"]) + parents_by_repre_id = get_representations_parents( + project_name, + representations=representations + ) + for repre_id, repre_parents in parents_by_repre_id.items(): + _, repre_subset, _, _ = repre_parents + subsets_loaded_by_id.add(repre_subset["_id"]) + + if self._doc_fetching_stop: + return + repre_info = {} if self.sync_server.enabled: version_ids = set() @@ -494,7 +527,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "subset_docs_by_id": subset_docs_by_id, "subset_families": subset_families, "last_versions_by_subset_id": last_versions_by_subset_id, - "repre_info_by_version_id": repre_info + "repre_info_by_version_id": repre_info, + "subsets_loaded_by_id": subsets_loaded_by_id } self.doc_fetched.emit() @@ -526,6 +560,17 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self.doc_fetched.emit() return + # Collect scene container representations to compare loaded state + # This runs in the main thread because it involves the host DCC + if self._host: + time_since_refresh = time.time() - self._host_loaded_refresh_time + print(time_since_refresh) + if time_since_refresh > self._host_loaded_refresh_timeout: + repre_ids = {con.get("representation") + for con in self._host.ls()} + self._loaded_representation_ids = repre_ids + self._host_loaded_refresh_time = time.time() + self.fetch_subset_and_version() def _on_doc_fetched(self): @@ -547,6 +592,10 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "repre_info_by_version_id" ) + subsets_loaded_by_id = self._doc_payload.get( + "subsets_loaded_by_id" + ) + if ( asset_docs_by_id is None or subset_docs_by_id is None @@ -561,7 +610,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): asset_docs_by_id, subset_docs_by_id, last_versions_by_subset_id, - repre_info_by_version_id + repre_info_by_version_id, + subsets_loaded_by_id ) self.endResetModel() self.refreshed.emit(True) @@ -589,8 +639,12 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): return merge_group def _fill_subset_items( - self, asset_docs_by_id, subset_docs_by_id, last_versions_by_subset_id, - repre_info_by_version_id + self, + asset_docs_by_id, + subset_docs_by_id, + last_versions_by_subset_id, + repre_info_by_version_id, + subsets_loaded_by_id ): _groups_tuple = self.groups_config.split_subsets_for_groups( subset_docs_by_id.values(), self._grouping @@ -614,6 +668,37 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "index": self.index(group_item.row(), 0) } + def _add_subset_item(subset_doc, parent_item, parent_index): + last_version = last_versions_by_subset_id.get( + subset_doc["_id"] + ) + # do not show subset without version + if not last_version: + return + + data = copy.deepcopy(subset_doc) + data["subset"] = subset_doc["name"] + + asset_id = subset_doc["parent"] + data["asset"] = asset_docs_by_id[asset_id]["name"] + + data["last_version"] = last_version + + loaded = subset_doc["_id"] in subsets_loaded_by_id + data["loaded_in_scene"] = "yes" if loaded else "no" + + # Sync server data + data.update( + self._get_last_repre_info(repre_info_by_version_id, + last_version["_id"])) + + item = Item() + item.update(data) + self.add_child(item, parent_item) + + index = self.index(item.row(), 0, parent_index) + self.set_version(index, last_version) + subset_counter = 0 for group_name, subset_docs_by_name in subset_docs_by_group.items(): parent_item = group_item_by_name[group_name]["item"] @@ -636,31 +721,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): _parent_index = parent_index for subset_doc in subset_docs: - asset_id = subset_doc["parent"] - - data = copy.deepcopy(subset_doc) - data["subset"] = subset_name - data["asset"] = asset_docs_by_id[asset_id]["name"] - - last_version = last_versions_by_subset_id.get( - subset_doc["_id"] - ) - data["last_version"] = last_version - - # do not show subset without version - if not last_version: - continue - - data.update( - self._get_last_repre_info(repre_info_by_version_id, - last_version["_id"])) - - item = Item() - item.update(data) - self.add_child(item, _parent_item) - - index = self.index(item.row(), 0, _parent_index) - self.set_version(index, last_version) + _add_subset_item(subset_doc, + parent_item=_parent_item, + parent_index=_parent_index) for subset_name in sorted(subset_docs_without_group.keys()): subset_docs = subset_docs_without_group[subset_name] @@ -675,31 +738,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): subset_counter += 1 for subset_doc in subset_docs: - asset_id = subset_doc["parent"] - - data = copy.deepcopy(subset_doc) - data["subset"] = subset_name - data["asset"] = asset_docs_by_id[asset_id]["name"] - - last_version = last_versions_by_subset_id.get( - subset_doc["_id"] - ) - data["last_version"] = last_version - - # do not show subset without version - if not last_version: - continue - - data.update( - self._get_last_repre_info(repre_info_by_version_id, - last_version["_id"])) - - item = Item() - item.update(data) - self.add_child(item, parent_item) - - index = self.index(item.row(), 0, parent_index) - self.set_version(index, last_version) + _add_subset_item(subset_doc, + parent_item=parent_item, + parent_index=parent_index) def data(self, index, role): if not index.isValid(): diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 48c038418a..3c4a89aa0f 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -168,7 +168,8 @@ class SubsetWidget(QtWidgets.QWidget): ("duration", 60), ("handles", 55), ("step", 10), - ("repre_info", 65) + ("repre_info", 65), + ("loaded_in_scene", 20) ) def __init__( From a07650226a3fe54eb3becfbc16881acf7e8ae6cf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Aug 2022 18:09:19 +0200 Subject: [PATCH 051/513] Remove unused variables --- openpype/tools/loader/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 3130f879df..6cb9ba2c6d 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -498,8 +498,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): project_name, representations=representations ) - for repre_id, repre_parents in parents_by_repre_id.items(): - _, repre_subset, _, _ = repre_parents + for repre_parents in parents_by_repre_id.values(): + repre_subset = repre_parents[1] subsets_loaded_by_id.add(repre_subset["_id"]) if self._doc_fetching_stop: From 56dad829047afc22ea61d5281660f18b47e9b585 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 18:13:18 +0200 Subject: [PATCH 052/513] admin docs for houdini shelves manager --- website/docs/admin_hosts_houdini.md | 11 +++++++++++ .../assets/houdini-admin_shelvesmanager.png | Bin 0 -> 28464 bytes website/sidebars.js | 5 +++-- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 website/docs/admin_hosts_houdini.md create mode 100644 website/docs/assets/houdini-admin_shelvesmanager.png diff --git a/website/docs/admin_hosts_houdini.md b/website/docs/admin_hosts_houdini.md new file mode 100644 index 0000000000..64c54db591 --- /dev/null +++ b/website/docs/admin_hosts_houdini.md @@ -0,0 +1,11 @@ +--- +id: admin_hosts_houdini +title: Houdini +sidebar_label: Houdini +--- + +## Shelves Manager +You can add your custom shelf set into Houdini by setting your shelf sets, shelves and tools in **Houdini -> Shelves Manager**. +![Custom menu definition](assets/houdini-admin_shelvesmanager.png) + +The Shelf Set Path is used to load a .shelf file to generate your shelf set. If the path is specified, you don't have to set the shelves and tools. \ No newline at end of file diff --git a/website/docs/assets/houdini-admin_shelvesmanager.png b/website/docs/assets/houdini-admin_shelvesmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..ba2f15a6a5eb5af0286a4f1485ddd97b351beab6 GIT binary patch literal 28464 zcmce;byQqmwlzwW;1Dc9gF6Iw2^8*5p+PG^fZ$#P5}@$l!9$_JgL{Qw!QCxTxVy_c z^sl?$?Y@2Qcz=A~V~mWlbM`rB@3r@yYtFe6qNXB?g-(Kwf`Wo2F9*^=?(&^{9d{Enj^zieS?OdPvQtu}F)vhC-j6K%#X^tZiZqpdePF&z-BxIRO1-hOJ zp#tyM@zj9(Lis5EZ54Py@l)e*5Q-L?aDV*@Qf1=&0Qf~^V8^M-?$z$U9465R{DgA* zG5Hbja{Yk!zNvF5i~-=WP*75d!7-0p=oE~>F)tGq^#V1%TTP13PXBcdF(`pHZ-l<* z&>^o}_Yelx%^Koo7cQ&5?<`^vt|8VF*+<}nCyO`^hyOSsu92-K%nUgaSu8keGiK!8 zucQ=ylx8f(h79dtsG;g_3rSSUfC}On`8eu#WGfDg?o8X3DygWv)64}L9h4~_J*My6 za0A=;OiQ0SU%Rn&M#eJ|Kzi$5A=b4lb22o5Ykxd)q~Mi(zb(-;Kls6>0vr|^YQT${ zr&g#9YAY{tXp$|Rh#*awXJH++sE0Mi?}XFASDx6R zuXW~$J#0!}Jtq9U$9Rn6rB7$qLZ4WfOoCuO#+Ys0@4%YLN!Au(Xgs|gM zM!=`OKG`Qvk_1!^dN?i^R1#!lj=5?^88}LRZn)IV*%#}~Tw>gZ#GNqWhOD`~)qU_% z-Sy-oCuapj`|M%sYs}U%R_99yrW7cKwkuo-)SPkH_{&`h#MRY|%_c+i7zdBnTjV~9 zgwl@YrM!&CWWp@K>}+g%3kU~oWo4h)S9z^q1A{#S(U+;I`D6LMV={KiX^f$tV)3;V zv@^cWV+S8STzdixBno=(m0-2Qf@KxBGlINW>rmW&o<4LbI;*p%hb1T%dwjF9IfdH3 zrHRE`_1!x;C#PROvgjUk;RDy6#)wT7%9S4pIj(E|^gyuiCJh_-*2UgFu~0s2PRBjB zU#O|eUCr3>`5qN;DHOl|I1L`z+@H0NI!Xq-{K3f=1YUkp{_8aRk0(lDAbOhJ$I%!3 zXAs|klO*_quKt%c-kIWTcnbb~vYrI4-p`=4^qCc#8Hw37;QjyU+iMNHjjTCsdtF5X z5qxU&>vuMD55I%wcdy~rnC=&+W4|ge%jrzi`JJpr8!BE5jZ|CHRb_7H^1~RNc1-kS zdGWNP{C-ZkSBl-SXr5l#g`oj=sO$p2A}d+XJk7SQU$P6jG5NrUL8Ax)(OyB^WDbS0 zf^nmJKkVic$PnFda{wKJGo*edXl9POc@h+)nbN1PrR8%<71JRHN|s7e=*bL{Nz&G% z7ia$5X&t%8=U}8#{$8DMXV(@767$PX^`#V>ftCYE6L)o`QX3l@l zrelufGp5#PVv>8&GF!0Sr_xG#6FL?TZENUn97WQBeqC}$*}QdZdm`#3i+(ARxgupMEV!#=J3t*l3nDjd7!bQSf0LC8Ha7A3R%Z`>ey`s)#l}lKKo#^|u}NJ!f%x#| zC^<_wrMHAt{7)o_5+-s}RLOaX*i6tw*u==}_YKc-107c*KkVDxvmh8Z}Fi?_ZA@?nl zGrAceZI43F8{GvS86qsf44xz-&rMx}B`qqKkA?09jtdaUNePn%Vd^0vi=b>y`Nrgf zdY_2UOO2gq^7(J`;X(4u0&kuZ>Q83Sz)OsQc3(ik9WuOXE$zAOa- z0bSQOW8Cy5iUbAao8tOU(d@sif!$Q8eb29v<(GK;U{n*f?-8K3dOJ|tZ!WwpF60)` z=?OA?LK#%_r>ry5;YDF3K?7WKZb-@{Fa>4`lk@%%=D0wNbs&W>dLNbXUO8zuQVHF} zd%?j#1_paks;CXNh{|A1rq*U~Vmk4nrVjc}F05-*-g$k|!;4RGX!FT84csCRIyTU$ zu9Yuo&hql*%z%lyo^ZF};*AXYM}tiAS1)j*f2q%P;{Yq~H^r?<(QKW<;*%%Ayp~>e zm+!r=X{p}h(oDW|$9SZ@5(u&g%V6D$BCK;xrZI|s3im&Tw`kqjLuE3i<^NilfYORJmgMp9^`xHV++ zJ#{c1Kc@#hvb1zm8&4?_`yQ`JG;PknvymkjL-#$Rait92oC*slpIX{X%Ys&;WE z8`bgfrbO5A%k*mP_}vkTS+l5t4`-o}IUSRl)eGRxj>(jqHos!L^rQAI*?TL|zv(8I zC(N97QX=M;<+vy7uksk^TJl;W<+~yKnz6L+6)Jw03zI8^48kMAE%kV%Ox;r)MquN0 zv<(K9IE5a;bo{3PDmztkcEOZ)IY!h%e`EIB@oz7D6KfV8L<6?i$1iLmu`|nbKKos& zeX9WOU>zhhL;;{ql=Y7P51Y(?-ksQwMYEeKbk4D7iBs>9h>Lmcz<)q^e?K`m{jgzM zN=iv6Eg4xW!2oPLgYC}(IyamqCWJ*O_5#~BZh3=a*0SJ|s@mhDqthUDOcx=bRiki? zuA@e3A?9AQ?^RQ2c9A1DwvoT{2Ne_}S{Kn>sP7MndFAoA1$pA(E&Y`vdv$&FN>EZt zDL$UpO&A^6RzG4a5s8dxpJYEqxiyIT_XD@^P548Dh<;jqUu(m~Gw4LHWGO`GV5uI|!kkKg_{6u$H@y#r_j^*o7Hv#+m|lQ3AT zaL;Oc%wGkV57TnkQ74$`R!J4?E-NQHg!sIul?)Kcrv~c14j$Bl0FwXt#FRNSj!+u@ z)|pmmmzG)8wJ0y;))vMHlMW_HwmXqp8V1y5d|J3$Ktr5>xp zD*BjehY*W5n36NxZZg!C1_CCXrtFZ3Oh{IVBc79qjs0PAj60_Cg?>f4_f=VV; zI}n7{oGeqsfAa1j5Y{8PZ$3S}IU17Pt3T@J>wL-Q;{ozG zPZe~Q%l-1jtpV~31qBkE-0G**={)%sLXtl@SbMav9%SL~k^|WXPE*=efeUbaNU;RYw;z7{6&M5K}eh1^6 zoO1WQc~etUO6b0+o12@|zWtsfU32u2w6VJPwqk@(_t(#8bDl%qiuxJ6;DOE=j4QMyDx0Ep1MO&ZH*s_G%$7 z-D_CGTD@?zwDGbV=yrOD;kZX=TwI*+UcF*W_U7hhUWQizo1Z!`;h09rptN4$CSp{E zJi}tE*v%HD_7f!38Wc0ipwI$jIt)a8C_^uZX$qosR%mYg!`f1N8TzdXrxu@n%;+W# zx`i%%;y6TLnRdyD*Tvjagpr#ZvzqlH$aI#^uHOp?Kwj_h3lciJFXnB^T%|FtErw$o z9Jan1IOY|w7#Ye(if6pReqH5<@Ayn&47iV6jZRTP%LYUjI~(qdil+sk(C3#?r=WEE zYv0yjmrLmq%+^$gAg(TD0}E>Sqd-&(@TphR#giwGE^*{2Qb$Wo?nX^pUr?Xc@XxNS zt>E9sN=yD()hw6u>hVkNNJ{bB>){+wrR$99eaAXF9>5k`CyKAmmpF;}(QF4vFWZj6 zl^&dU^5yT)xwOFru-{uJVyBblSuzpzTP0K8r@NkaO=mF1tF`ykLXJzHTi(5b6KPVz z?UH6F&aWPk^1YX%PKZ@;c}f~o5jUf-~{)b>G;@`io*#0@y0tbJT!avSg}6f@ej+Lauf(j zh=kt${P-&%0A{?XoSR>+MM%Mz>Ua{~u9zSq;xM1|S9}a~Uq3&}%H7QBC zjVl_CURS5+DydsU>_W4Wadgwq135U3$!-0n-jcej&W)*7xLU3?hK@r#uFoAC=EQam ztpUz~On7S?oRpMA5fu#6&pkoXV%aCGq(3?t0O8Q_>uD+`1*@!b>zU_}0KJD$Dv2U{5n{SGt z<<0o@?)CV@7~*JG*d|^fYpC*29|gth;q2Nz6kxM*@WZz2bU<(a}e^aHfpM@`6>LWE}h(3uyzb%fQ_n9p`Fe#t7 z9-1e@A6YFD7ag}di?B;y1UK$y+|6DkB4$4zBg$F#ZavRP9a5q1R+hVB@1TcW-@bjr zBIVQ4*0!5JG{1XrlgQzxP0(H}esg@#Nm;{nTU~5G>9bF@5D~g+8uD(Z(>77$Dx9^) z($X?4T`bMb$LAW@^O!^^04hsbn%*i^l$VdT z^t?LVQCSnTCwYK!%yyu~M2$^A~nVi{Di(XejzKjE7h4wGXKZYc%9b|ySiZa3 zy1DzqWdRyxC!|n>Hn{WBl)7T7al73zIvOvf=DnD^0}9GJd?t}dVb`)IxT!dR-Bv;j z9lCki-K8NLBj;1Ge1+e?d+z&-UzdM%L&HEpiI6wFiKjzU>hjF4eLT&cxkzWQO!nCz zfer{*&TgjDQ~|X z`f}&RJz^AhU3B04wf8U$;EPwVEvs^0v)Qhjjm*r%QC%PwGZwzpP- z$6sYod5ZoRN}mib(j(?B=PSxt%(ILp@FXDUr#%AMId+*S9w&}y|CK(=!~{A?VHJ`C-5r^5=?EzWOB?2z zqSHVER;vdbb*sAMz2#wqxsAIYkkS{EdC-T3QV)E-veapjoA221@$_e)_OBp2I3@XA z|2|zz>LS#H&JGue_abrZe5WFu)%edSkQ_*#6eUYp2|gDMXT`&_Eu^KEdA?&^=%6?0Fzsdt9Eo(TwR~uP*l1z=&daR01NAuD;^%w zmD`vf&+`{@+uWW(rzN9zV#m-<6){ID^bJ7HbLmyeHqT@ZnN@G8Nxbo*cT~^3q%QGF zhj)Dv#eXbtuBxb0ZlaOCyLOX5I`(Ommg4BQI`H*Vtbe<8j_A_oMwV~XH1>|^);{NI zT>KH!{c0;9zI|r&Vu3HxS`}gB`$zcJBAsP}OLthy?8R8A0Z&~!1Y*AMiSOxB(Bst6AMLP4fvZoa`^P$RtYBg_UOf`7ehRPq_qq0~R7gVwh1I>gZKG#Iq}0-E*ukJ-4kA_cKYvMfk> zxG<$z4&f=Nt80^X{cJ0L%(8hgM8&aPb#%vPBAHWFhtbkZnKZ+Kyo&sQABYil!vTE* zb@BnK_-g31d3wHuNm#EFde6|elc;@uN=L5dpf|vZ z4L?*HT#LFy$EF_MzMJMfcbcJdmNtKkhu1pY+0O^PaT&anZTs_171VgThEsQ0{%wHL z4mu*KvoU)!a&hS*D%zjT}WEo4)rA+?y1; zU2Wsk_eNN6Ufcf71q2g~KdVx)O;~>0aoX;8mVJaQ~HEUzjoMV3e6J#(uvfQPNR^ za}r%!v^0l(;)<{eE{>cI>v>U|btz_OxQn=5gWiQwgA>LsS8r0X&t_&74h4LJ=V6aq z6LeuPjwu!uk*4F;M9NoS`k^dFj^m(HTZ(afyi$<_DR?lI2KoF(gpvluRM%MCYx}~z zVcOyl*ym=NKxmfyu^clw(_I;~?HT(LuktT03St@okU58Z4uqW72b|8pJVi=TI(0Ti z$zIU-#UcbIkRm<&qRI4=R_Ez?%%b9vfs(4a{wqwft?z;@+KF%5ptt;-f0#; zpqrB5@0xpe$&TodTjWDPxwv0i@%|?~?W@SV)&>g@a>#;j!e~x}ZmJ1Wt7ebee=X!` zZ0NsO%IEcSF=_oS3=PP};Pkl*0M}UZ?y;A5bb5Nfj~F@mB>(6j6&x$ zt6wid-SOvZkdhP^t+#eq7tW}*r5Xv_@tBK}8JTdZ&xI^>)JF_B^+@{BKE3zT z`?U^Tm_RgSrdL%{Z%A6G$b(|oVR)&uJ$+Y<3P(oru_1C+dSE*NMV_G-Zvce|E*9=+ z05*v4wQXAb0ir$9PtxQvelz?PW_=sDz(^Mn;;Mxoje9hM&u*NUE!=5PFF3%cidc@mvo=hc$i$Z_>xsFHJq}d-RsMKn zv|b?1>~59Ycq>*ceL z?EU^i{x$T52buqO>^r@ErX^c$fC*=x*5`NiXRvwgXGXb|Pea|j{n2@ut!hHl;d-G0 zlAK(nqfoDwwD_nUibI z=JlRUiMKy(>E$1tX1E-WG+&am1knf$wWX(j&BUt?C{pm3q-jwW+KnuVaivNZdU7pQktTN+3m=yKT6I|&mfO!Lu8?Lk4^TwZ2rSeGm z*mi1}rUL=yi&wD|RR{y+BYW?~@72MHT>jMR^L876XSJ?XaX2K8(@ot5+p^4s(7S)K z-6w=xJ7N^d>~sm9qXR3tup2-qt{Z!ePb;|p8jC04XGb614x&tWVq*h*4+j`nslElf zX>-T;;N^Y2TsY%B=jS4?;X&rU`PN&ybsF6T(v4rY?dG`{7PNSV;ld8knjr)f4D_l;N}iy^8>n}C8*s97W~s@Zg8LH}ynlEBet+oI zckHi82PBe5|G<$%&_IurOrT9wqjU1pT7ds08`l9qTh>4N za$FuqP-dRC@C39RA*V(Nd5%ZpX_*9Dwra7U9=7y*dwxLYzEF9|a>vGtmbEOAgB% zN1Vf1LrrgPoB%@<) z|6?#SIn~AllUwK4*FZhUxrotVunq^&vS`7(2R#me6iJ9`dkw`;lVaYIR9M#4LRTuR z{+;dI=o5gWybkd#%8G=!fD4Q9cipp%JEz%lOK*$k8s^iX1}AJRAr>X|ZM7TV7)!8u zK}PjQ0Xk37&BoHY%fp{WfViJZ3TXTEgMu#KZg1e&luTGIM7Xd2N=hmJmI%H6Uvf`i zzWo;=U&6gZnM7?yAnVO?ii?xar{o7s;6Ma5_Sik9_9u ziR3B&>f={kn7Y1O1woqflse+BZ-V>tN4C;ESCdXd6ZpK2d%+d@BS$4u6~UO~)5b@) zYk|aC%-Y`4k5Kk;fRPI+J7vD9jwctAHj^|nePhZZj6=!!%l);(d}GTeRQ|m$adCQI zG8SsrlG-{u@4Wu}ys+?+(Jvm~I;o%ArQd-ll>eyFEj=X{x!!LrWi^3LSteFNjT6i@ zH8oRe01k;5U5&A3Kd^d-z&bw6 z#ZyN-ajQ_v{ot~@6GrOm4;rB{Qf> z;tC^7#HW&(p2x-<9v66iE((w`T;g$I7cM(pxRO4rM= zuAs{8c4B8|=d05u*jjv#u}5Y>!De|6d&B!~f#|vu8D=I?dax^$Gkskzh)4B69*AC1(?@rII`m8GYth_G}cWfLS4DipN zA1^3JTJ{+GtR~SB5)z6GlzQBpG(ZNLx66BSRZ==$_a^kEOq7}|0E$!+H$?Pe84_*hnBx*iKF00+!Yx3Z@v9EM z=eyf+INLh8tiPR4hST6`q~*_ zn!4PnNf|h~J{riFFkwJh#{||Oh8$K7S}-+rzg09(2a(tB^4mPZD(-YZ7VFe!F(A~; z(PAWUOG$$T1OzxaIn9HcyncN~~T)pf0{^D70+SQ)l zO&IJxNRKdJ@3zJ>I>KKbO+hkpFkhDMC7MuEQ!|+GT`YPYfz%Bv->DlLLt__$m+{G- z7yTgs`l`o088>5p>T&?KerTOIkmemlE3YuN(SV^G(|B?^BW7H4DzWIlF#<6JB6n-N zn1;HAtq6LxbG6&xu=p!50e*W_jYmXBdG2YAQf743VE>*>pDN?UjNga47 zT(8d$9QdH-d?^zpM#7$l%N^o26fIjPd>M>9Je4L0=z!?edrQj|nxi!wKoA&67j^GT zziGpd65fvNr0^3FqiQaxF6lBXzCb?x*#8umG&=J#3(c4`vDa5j?w5h?F&aG{bLF`! z?z?=%#H6}f$F{B3rf(kL?aVctbr+V>aOiht*xGGKfJX_4Nx? z)xovDhoTg2Vm1VF2vuLNO%D%qT8t}pJ+w<`u@qEvcjn?=dmXl+qa(XVe<40 zbirrn4AeOqV6{P>F?t0>Mb9U9gHlIgH!e3rS+cRuWBx@-N%Qq^?xWEH7bL5KK%J4~ zg+)cQaAarXlcZ0QQc~WsXU4VroYccxCsZv|&A=X2F9a;Wys!JcIDK&OY%pLSFr^LgeKyA_^WyB1I^0I-O{_)z#G}7CyAL9<8DU>>sj} z@=&ty%v2=BrC#^(FYs7veNetj(O@LdFV1alPKU9^6Uh@uwMxdu#F&^ITmn&RQJ0xu z2Fo8}Vj>ky8ZLXt#mIO1cIeH*f%jfBDLy{8^LQ^HCIX`4n23B(CZHvD*JL}X7?-*O zfdq)3-us!sVtN%puyQwxV*QgVP3celeiK%)&)8I4fUxPfGJOfO-Uxr+k`=|rBO@b| zem09Tc=4x>@Y!pyZAtn?(4HS;BUh+i5i|08M*YupsIUWIe<{i2j_ZW3+Nh9lEtK7D zaBw;AB6{WJ<)3&y!OEkNiF^e=<}?q*CVCkm6FkTMOwz)_!o`ILug=I`^Gk^7S0X7# zvb>QE8(Z6y<5^&x`^sq!dk08)!8nxkaQ{zFiHM2$F=wmUjxM+C8O8LrPrLvnFbIb- zSBn{(piC($#KD1RE)k>U`3%!bh=_`ESbY8*|L90{&!3khyfcc@Z)HXIDMMc6*X7$r zk%d?$)=rWLwqjLYxXyP#Zk%uO7B_C-V-o!St?U(>UFFbQT7Z~W0Y=?OmkSb}{f`0C3GFecbh8A&ukj1eMdbZ#US z=Gcl<#`fXyp18oS?QYFgUCVH0UpTXO?c31(j23`POe}8R!5c8sbX=EHbyDRPj1=c+ zRMXK(NQqSh0pm5%Z^VH(?DpPknh=@(>Hi3aQfOP)e)g!iMn{y#VV-xw*tTNI*z>}Y zT0ud<-pASU>a zk|Ij>*bC4L6MMS5$DxgGT@?er%MEpOs@FI2FfcG2l017bdJ@>cjgvJ5PHufPm=q!& zfYKYErvhO03kms{+oS6v=e;J~?^<3WzclZlo2kwZBocqp=0&2pV3X(jA@W;g6#$I- zIQ*|f`6?0>o6O^aFcERQw4s}cxH;Vv-mPuwi-z=mRX5SSYmE|5tyn`wiT^Nbx6M;Y zJ51xk1cHR+?sDSFXGWkOqo*9$Sr7Kk7X0Qn9->snA4T_NmMS|4*d2tOz;D@R6Hf%) z&W{U2x>;0~#88h3BP zEH<}U!uE){y^X;o>98AdCKQR3f2u$tH4o>K?AN^0zHT#`HNgqYgM_B&zLe~y;dPH>n%)Hulwrigi7 zAIspqZ}CzBDRk$ZmM1j!<=D;o8l)jG*BKZr+LAiPvSWg6d#m$hX$nlQ#Wae1;kAx6 zlbdv2eOB-~IzL`3ZuIFNB4jfM$x<-1VUhw!Cb;IvlL(qmn-{9Xfs44v*A5&A7mkfz z2DBz~-8+dzp)t68Fud*XhCu&wtx8o*nhdJL^ z)SiOd-M#d!h~LOpgC&o(vQy>Vu>!q6VpFkEyh;Al?(prY>FbVhTUM6FNVt08Jns*yRb*AXT~Rx1h*2dg8=8`+VjUzuRIzl97>-#3y+gc z4h)5S4*JT>V!Q1|06aRbhkc8tkv1iXnN8`(d_f_+G?g7U@4rdfKdtQ(WeYWHba{%3 zU8u=AT~9>G{@rxB=3mGP_~}j><{f6&38kg4L*i52$v)FdU(1Bb!)76`!-6-S?wc}J?}6DmAcv=euk<8Rio3lBJhAZ!-2hGw z4h-}?Zwa27n!+;0p%BSmj;GfgtQZg#K1=t8F>-P`cJNMIr>4!(vf?Qxak(yKqoD_y z-CUjk41g~l0eaw{135Sd1h{aTJ1U2=ApOafTrU^&PjmpspX?Dza|0tdUPE;!Dl^=7U?0li!5II_E2aA|i zTp18o-f|B{KSdN(5C@CeXncJvd#t<_44`j?HP-o)K~dSm(HhDi!yPo8GJHJSihV+9 zP#ahkOlFtNV%U5aXG@ou3K6;A%Rn+?w86~<(TJABlR905@1mm{Uu`fCyLC3KGUQ$wh zY_S2r_^_}r*_<59oykl!#^lc{o?>ivqt~*lEb-lmCQwVrxJv1SBFNiqulX>PCEa5w z?ZnF6Kn`C_(w>@BAswD9&ESn#8Op0Y15 z5=rh$TpsWBF-p97fX8WkMk(~#Vzzd&CVN<6y2yRgN+gSYw#~!vUsTkCWAhTfuabwi zd^l2|zhv6+RTZa-LMmz@H4ljd%hHJy6Sddh?mjNJ$OB`lO3oKfW#;ESV+1u8!_(H$ zAqn>~>0#rBsAO;=&kv9T97YNtgPI$cgLY!zFY1Mu&s=>J2Zgg^Y}wPMOu(HW9Y%~F zO&gZ`N;5tu6xL|GXC5WWXEU1Lu$$Zj*-JlL@Q#9&`}Nbq@flQJgg>RW;iN-Bsg-z8 zC>YSY3T%hRPe17%KaUR3oGcno6>YG!wGn#vHj!pS@R(i>uv#_0uO}@X-D@j<$hGwH z>~lgw`f;)H`MwAx35h<~Gn6p|sp=P2!OGIZh38vlrHo8@36uNpp`8Xk@+!8}wr9|#$P_tHs3z*m7Gr>REh$m*Trwmju;|)vF$L*@JC(pOyfHcU`YEFoSn|lO#fUB<1jTy%^w>FDUB3gq!f7Y|p zX<_^Nsh2%Z)Bbx*&rHTbP#h4a+Yi;8EtJx#?-ElFhs)`A*b{A!&K9IWGFD1yr*r(I zGmdYGb3`g>&Icnq*jo1HSA^B#h)KFJ0AM&u!Y?-0h@-1JZt>{*VH*dQ_7kKdD#H76 z2nX3A9ns)u@7=l?vQg-lLE&wYc=(&0T0@KAiSOYDmLtDK@w>V+4!^p^4pK|-{LRrW z|GdD>V0Tw6!`;nt?3*`l06r|gpkTUjrStaDC5Opv0b{6v-{l67VcJ_>+Zoh|o&S+) zCME1^eLgfDHNozA`VyP~B*R3azL%PzQBeqFzc8J8x$yfQ6Y#v*GG87bSV3BV^!8xQ zXT!XcLeMAQb8b)%WMP4L@F;n<^4-kOzoea}pQ$&www5#Zr4fKYoY6D@7$T)4rNpoQ ze8R|Jr>D2-PZh)2B5Cgf2OgAbE8YTW70Rs0fL>$#SvA31l`o*EpNF`y;fEF z&ZqVMu5+&9hv z=h!V$fW_Hha~}^vISHqK)RIx#Ed|*2DCvPw?=dzcdinB)XV0qh*;AB{I)t6Ut}p!rCbd_;hXP%vdX~I0&X7wVVTvnaAwn(c5T z0sQ)`=wnkxR&}n`(V3kmy!>xB^XlWnzv|mXasT(y;B~t=0I2@;{{jGQ{WqiN-^=WO z&P$=+YI(Mg00o~xnnpvb-*!qfoa5LoLhGZsv*y#31MK2jEJE!{a1fpcwe0>sV4v>G4HJOVD|Y(9zYu>gca-w>;=3pyU!BHA>KaAhzz)`f|pO&0EKO{Tctrfw-G z-2S}J=Y%t6#jO#Tx9fss)x&fb-Pj9n3_2=i_d2;+<)}F^e;^3I0dA!5WxI8=UT|KK zn0|(r)R-m4ujX@96V2h%(FcG8`^z`7q}!&d>c^i~OKh@wHGM#}vGY$o2!cl&EY?+C z&blz<5T13XXS<};4=&6Xof=%g;}5}0t-p@oY7MO)bgKurm36S1^D!u#@v#F`ee~%> z_Cj&KnEmtxA6`-oBC||;G_px{)wLzX0#Zb`e@SQm1Uw6n#T7A* zs?q&ihqS@oUi-+0j>W01Gd{U`%lXJwshw(U zv*D$;>-}Q7K7EU55{|N6(x-fS+pEO)E8&Y8ksV9E#IvOU!Z+J_>>sCX9qh)YoAs-9 zPwM0AiK;ZJLW|ADbnJ}0j0!c1n-J9{dJ=eX?cKT5FbrIxsfTD?v`4piB<9b;a6b*M zU8zE_*F@H+^gV#ury$2ewKEcJp`%)lGl8C77%%GPYl4`!8VRjpP4!LqZ=PlLfBZ7H z`rF=C>rlVz19aRIqZhU%s1}@geD~4S)Yco~k1Lsmp5&x3b?1xGXPIP;Ya`5N0;|!& zmHgfK%=7!Wj73si*{Z~93$E5iswOkP-`sbZZ5`3~>Q}j+aU}s?&6ePosV|4a$^Nl| zXPOe`A&_^{*zhT5D{oKv%5Xj=rj4sT_rguBVhmo#;p{(u-Uh;TGSzlx8qP`)w>GXi zDb3Tg1VWQDGb{w?@d||GzDs8dPzN_Hpy+9;0s;u}Q=6W6lI!ikyr^8H<<+T1aYi$1 zz#U4`DmRWX%yeZ&=E`a_oefcVNivc9g#Oe_S_JJ%jy_AK z&u_yZTPeoE_XDzco742USIZ-1g=lCd&grJMDvuGl;ugtvQPthmemAK=p7r`vV! zFAl9KeMdJ1!o-a*QNj{gFOX*oKY}ElQAz1Jzp5~mBf7g(T|s7f)Ua=of-TRs{fre} zPj8xry(2~gYv#Hg;!=E7DX{HXxawfuiJPm+LSGnC7~nteVz7BAR;eJ^!rrZO3^iC3 z4$bg^|EM$eTp=VSO)>Gi(RTOxgSfjf9Z0{qI>ma))z&&PGCn@u^5z9Gsr%)zbqp<} zFMY1qfFEV@hk57C^)-+!#SF)k7&Qn92>}?1mWfH8^0Yrqn2fJ;-nfj@3AqifK>0Z{ z=ffD*9vT@L85QQhsvQF2^6kLnOG!&isX@-{6bNazCR9U@ zpHYqCz3m&KewbNSYT`#~V4yGc2qny{tFTZrwp2GRQ9RF|9PbS-C@nRE#0X!Yo%y1+ zB-+hGdr_F=1sj_iq;_0QQ$|~aR+|?^qFZsQdydhE1 zCY*TOH&})1G8a2PeYr657`Ogz@}|0Z!+_Zf^K}OsCW?fc4qxkQ9%YDym7DwC?ug>% z>gs%riTJ~h*WS)e;m4RLj7&^Fsw#%6t`Q;l3l!X|w>MWNcGHhhemMF8J1ipuLmnby z0Z2feKEdWfx3x9DI%MG}H9%0HREBpLzcta(AAc?XH0lFa7>u-l&0jRkqIzrD#Tv@FW1qSJZun3}dE`V0EsJ$Xl)MWJGr zP2h`M#F<`TU0@>o{rT)R&Qq>r7+$E1bO^#%-1?>xa;a#20*D8`_HnTt9eR^{RLzbG zyycs`M^ABzshbW5>Q@KTnCW1JQ4^3*TM3#f&dJu)$dU6ag|EU^uM#Z5QRamTSE-^y z5EiE1yx*A7+L~;Z3m#U8CV6d1=k)N65Fi%x+YaU@hRu0IwZ4Zeg;PG`s$NR<7qRk_ zDy*Ii+mo5WslX;toSRE{yh7_sE*bLqXz}aWnM!WSk*u5)L1 zHyu$_x;qP^BMDC`ZmAk>7Q8GRP(C-jlGAwxeXM-q)Ldw_7?#|8<2KMq7U{M7GEl!r zxBlk(^cbt~h1-#->?=?aipS%^L0w(FFDrCscGh|8OeJ&Z$IbaFI31EO^MS{p-gJm2 z2$Ph95D)U?I~j>5=4L^EtEN_CUASIB7#JK9qEQ7WgkIF#?sY{x$I#SBz!E`qbnus- zZ#fxQK;E}OtS(P zg-UWD$1DvcHw~d-Vw%ZUbUgw8qcTF$zqov06w<)T# zjYJPq(~=AMX`^4Zf_W<|t+Z0(o}8S9^x;@Kxk?9TDzaD{-KOMhSRF1_W}E250xwoF zgze0}m7r1_MFwz6WID)KZ-8Bj=HHr1ssllp65w#+K3NFv_PH3LzMt_;fy$YX21~^0 z(p8=k_kn7mj;P7)jt@YK4ZY>v=|id?RBAqdvHq?tOK1Sbb5We|Jrq;)c3ywB;C}ou+-O$ODHX+5|B{fMTEL{{sOHoY)nR@8#a>>s!fI-2#X5~dP9LQk z+1c69-MKn?dpCi|`*78YC~Yv?tgfhNU}~z?VxkB}R#S|z+u(71JTdPvaYS=fUgg^!f{Yk*6^qBW}vF|>;I2OW63<=Sp z#Wk~5(>tjD;g1411$y(CB>>O4*yU2@xVK+RW-m^9gq>X@njpEKl-tR2#G|2s5~$ey zC(HXT=xr6t9(*v0C>IkWBZQWP?|$`89#f30$>1J$*eO`=%wA$sa;Az#R?0j4uSmiw&8&kv)s;#{2%=?>kRi+%blV;l#{yBD9E zL(gdL&dWNRu&@_y%cxf##O-r)*1M^Vz*+;f{CSV&n^Rbr!fOAmY4!T*@(`~N|3Sd} zTKEk2JnoYx2;Q%r4QVe=mQk6v1u_GnJ-L4lOnKP)2uQY;h#(Tf{*jfCu(oFE?0g#h zm`3bX0fIMx4?t}O?iO)zYw3nKp7%exn4dj6I6TY}m`fxi1co?b-uJO0xdc}i3$jsB zIKl5O!U!$7_?fwFI;&@t9rFTV7=jp4k`*rd2AJ_O*guj61&fP{QaRmKiglWh5qjg8 zKyeKXaq*QI!yAu-)fn`)53e$wOGFTOSXWh51)8n30?p9H#cg_envVw+T=fP_9hMgt z`BUY_>jJl|yA9>m+tXavS;DB-hI(L;NM}}EI#zCsO_(EQmyvx{GfJiUqzqmKgsnjY zc~cg$g>mZ{;Ts{fd5ibc_hhtNZ?*Qty>4H8ug0hn-f$m6Mie$T--x_C#d^wj)QGn0 zQNJ`J6VH?a$O&b43cvGHL8q;~MNt6(0hxG4fb~R!%G9^AWa1pQr#Tt7-fH(RuK#h| z9EU+HTZDLcPW!C^lap$Kg3apXCdKDh_7GTi>}+fZ7V?ZIUxaO^<{Ug%oNao5m&UOurxfIZQwuR@@YAOLc90F_w=;DaNE$ zqj#J)#+GP#u-u&~(d>AzJSYwuqLJeW+YTBwX)JlCba=@ool?nfEm{a2*C;(f;`&8xQ zIV<|APV{y6A4i=w!qy+UEF$VwFMJDhrFyWDSzsxhBW;H#+nN_61%0m}NhzuIsd5?! zoq$DeKbi_G%GH^@Li7`#F{!#ZWC)8)prW#TtWvUZ6W^ZTAX>6Ws}>RJ?(W{%*{Kt^ zvDxkGLx(2-2O&iu`(R~_&;_W#+> z`y=l`ZV+XtY*hK08p#_#A~G_wHoTaqx6y9{(Vz0Iw_z2Yk3;19aaZT7Y0wu29w0zZ zm0v7%2yn-gm6s6)rkn9SXD_dJKb@=*1ceNdq3i1_oERKpVlV;XcLeflB_1m-3RE$U zD3V2dBILzX;gtfQ=IGWzUzzq^m4si`$1j2XF1jZt7eFE8%_BrPLd zNfbV*!1(Fnm!wQ4ZM6Q;lg9|q{p=Q27dA27cSlEiHTIVgL%6tNY~+heT^Ac0w^77S;r~YF zZW9GNoiKWNNu1x#s6VY6r9RR09h%mf)_GD_0dK9+XcoseE~6(^ZmO;OJuBPL>`7(O zBb2$aO!X=9_S=A!&|`b4tl_>R8fB5&i3=nlA-#od{_5?mEueHN(SHgJ1=~p*`XZ-p z!dyZ_lxs*EQdU(}l#|1<;d;2z-_jxktpRtlwKWrTv^MpehDJh=g^9`Y*W^C0^Lx`I zIeGch!_Ce`Tghm20v?u^FEjJXq$%+bAgO2_9UbbXdp5gaVGgr)+S)08lBf_MS|=w? zzy}UtbC|wya}x)vU_PKQ940$rGVmFkn(O0*GuvC5!hR^5#x&jHxANRqztBe(G!KI? z$ci?XHfNIC4fb6c=9(IT@%zaGF=65Fnl{&$J)e5R1H@3%18rZTu+a?6GGIEV)qNlI zjAeS!BkV6%v#?+PU|;=TGnc+w7gGqcLiTmX?<1uyd+kv+5_uQ+#6gO%*Qa-Mg>F+SPJQ z@%VUnT%RX2=^F{`6`V-`iguVPyYVarC;i_LGkP0AWx~~K_~lV5i@vX7uFF-;RfjHj z1yNy8Q0K{0en?OGOgct1On|$M1C!)z7ex1%KMP_(k)$s*9zUYMMwLqw&{k3TwpsNT zPS@BOdjrsz_ue}3HDru>bag86%o)uH6S@v@3Rzw;6J42bB$+vFy7E0GDYoJ_18lR(dzC8iE@oQzH zv4Bwgr0u&pKPmjp3`|VpDLBZb785(SS0+W$Mh3N}Ba+d-aGq4^-CUW-O~5q{YJn;J z!;jz3gMjV1>7R(+E25jipHZxoNBnAb=HhK8*{*EbhJ$M@f2h^(`+Tzbq_f4h_}Woh zk91boED?QuU0&q-pYgO|P`bR(5&Ef@r5J@xW^>}REwXVqY7o(Z&szZmsJhi4vZQVC z=&zFs1J#-_}A=g_F#_JMon4Sdqak*cUFt59R$T& zPeDnhzV-x@R7{Fdsj*R&nHl~B(_&Y1Lc(#96zRP#1#X+Kcuxp)JN_r#bzYKCo?$E5 zc2WPvoo)VszP6Xg6<|qduQyk>FG2L7bHY#a%EQ(Ek+h;S%Ye+YEXPgFTK@Lgdgp4( zsb6)ae6_W;3~}7ep~0?b0j)%vq~T~qDth3?Lop?HPFrssoI#tW25dZhzm z>hA$)-uPvj7F7`ct1u>&anQWI@e`3NRdT;;_p!2t1ulSW}#Rq4d> zcXx)AAR*f2{eq+-V(3dw$(v`#k5B_HsE7(lOs1u&LrsWbiT_)BDK>gxT8h7N zoZ6b?8G!k`|H)QJ?nyiZBti=CIxhHTLU^HUTX=e`G}WM#OGb;#Eddh=0;c58j{9>n72Am-|7h?SZ^k|Stka7RmyUYD$_H8`m9pqe^8 zwi~%4G8E;lpsOry?2^~*!9$rooDwt?#YHRoF5(RA%VP(D$pZT> zFN_K^*8kCY^tMqq%(wSnzdNf5#HH|`_NVXG7nCfQs`w>?4!(#|F{YRsowQbVXO|e+pMw?n z>c99G9osPz>q`@S{47QNnojY26phq>QW--`KxNGS7nRx9K1q=ffy0XhJW{l^h&C6t zeh=r%&j&JaX}tW|`kx$Uo&<25n1AIsW&eh?$qXL^xX1un*%$gT-rVX{T;jvsLI*Ci z{e8rA7IM>)#T)G&4YBgwGMnR;Z*+};W5}|G_^XpCI%6B;;iXljI)qK{r^<|sgI5G? z^*}lV7Aen9%R#e|Y>+E)Ev}KIzH;q$xbmboeqwAak%~wJwXp3?U}T~>Epr^!&*D&`dy0nqz0t9o ze*0_3T$b^krHI0S8k%r^6@U37j*V|fZZcWx>+7(@q<-T_V*aXHX_5CwT|>t12QDuH z^)+hr;pLP#y9EV|(Aj#|P~HZ8`Hxqq>G+QUV^l;fy9jp$YE2gXLxSk6M>X_~qR2y` zgn_wk48F<XhXG zNLT_fagaA5DiNkvsumC8=<91kbZ@ikPS=`P*$&pCokQP%s z`3Hde3E53H+~)?H1#P~=f%zc5x7y=SRzcwrBuN>qIW+~97|DzVV(9j~W8+G0w4W#< z9LWv}42&S*3?cs@!^#Q}Fo2T4F%t4Y4G98>d!V;BG(0>!>{qM{%x?KZl>eFqp!dXPW-9O-ta-0nlG~q;M7p3N3NgHl%12Q>^m%0<)(R_h zI{Cev?JF*8YG%EoJT3MBQaVSCi&FP_%znDqEwZS&XSNamV@=s>M zYqSv?iqFKDAb7jFwg#xlrW;r%goKF@Hu0s2o*p@L_BU1n(<)i0oX|NuI$}$#^6T;u zEBz*)9G8}c1>rCUFK$yrQ{ppfmC&)hAK03bh$OLd+#Y8679T&6O6I9n`5xpKh=KZg z7=F%njePF(^HQ=jY|oL$oluF>?+BiBhEv`{%Im49P(Kn;l8W`#YU&jn;K(Q`g%1 zqTT9AO16nh-mp2rV`$X8Qg}D}vYc)fL24X}W8*cpf85Z9948S5x&Oy|;N(eV&YE}= zK3TYOoQRxRSzFOa?_hTBZ&m4OnA?M>(@}d+ntZ1x)jEg6>aPo;vJ}mXFJkCv#7a^j z2(#(#e*~o8T3jQLle6rdq|5+!UnM6e{imMx_AI~#pf}kC<@l42!yDM*GiWU=HbX)l zWTtZ4%#F^zT>ro20?vMZlv2#hkE=Dl&dm`L$`|^;U<{8?kq4Ltpq52jq7IHkFVi(t zruc{ds*!8Pmqh*kZ4f27Ozr@A&w03U->g6L|9HMgq<941FJnF^W8R3EiX0B;#D96N zxI)&@CwP>tWR!l*yjW+LzicQQL`63|FtB6idX1+K1~xKc(U;(?&-nWk&++-+{RqqQja>KpC~{XtN{5RQX)GEl$U^XFiDrV7DZ zX~WRP?$5!Wv)s3*mKWxaAzWPC&*w{9D=h>dM{qDYe_zW1T56>Jyw zNhhq&;|UDNw{dknP3M zIuM!yQz|vKceLxDje7z(;kmb`KSIWKCn7G6aF;aNyF~n&)v`Z)FwGwt*J_!p!nYbb z_Kl)$iS(sg!+9}xwfLA#K$FeBq|Dq@$PPa&qn+g6Fz$)Vl+rT>_KLTK^WLUC#FRZ+DV;ai}7qrejL~ zPb@y##X$Vf+Uo7CkPtxObDF|WqeoYB+e!Khxvc`hwuzd$^2Ld!Y>zPT_>KU1USG4Z zjc6*^Lf*;AGmQ=Ig#?qheorXF!6B8wPq47-#Vk1AhX!!s(%#&I4VIkf`mh)jih2yF z`SV44Sky$J_Dr-jF!dJa>6e(7FC zHS4tf>$rhk7}b$nL&AB$kN7Xp@&&W)VgsRamv$vQQEzi{t`>ANDBJO1^ejvc)PM`%!j?fDOY%!)!@1KtBibh48lkjipB*g88P1 zqO7&K^}JpOi?Jh&wbY|HG_)7ggRRigu-aSCPP3jbea^u#+Io0#ygPp|0CccjAfa27 zfcPlN#K#P$FHQ#qXrB=&L$7JUJMi>yKQ0kdl1&uVzu`f(olaOBOqU)Zy{60PIBw_IEK_l-<`m*lS>?xOLo+s|{ z&FSRXlTY#&7G^Vo;}%<=^TY!mdL^9}jJUeG$|mxC>NlQmaJ9c}^0+tw0dY{+@h%|# z;nn5Ij;y?wV!!1SjwAbZF|Y|IW~h1TT-bZxLMA4wAjF>a?c0NZRv@R$Oin7PC|gf8 zsHS+QV z-v$#DG&S9y!}>!8Zx=6j!x2Hu9uW%aP2{yunPAf978frHKg-zc)y^JF_-DgJ2-#?^4^*Bfm z=$K&irKP19ORp38#u_C~!=s`AqNq*t@|yEK2kuj*c$>?W1*BzVuWfsl4LiMh26{lMHmgn3xv_ykMtEU=M!-Yx zKNV={c_`2RMUX}G20}Wh?h7(0fz)&|A}=AB%FlgOz6T~BgiDN+pFjlnUU**dKsg&q zm#5pw{La=cE<93E5Am4P8qI;Qnf$`_U~R}oERAQ8{Jow+!TQLzb^D0$YjzfvJzy4X zYIMJKTna2NmytB>%GTE>3O5FF{rWngd1c-r%{a>ds2>W4!{eCL5g}0L7@t||LwNY{ z;bAhT`w39#)7TtC6l6kgCjn+fF5rE`|0OSU%L{EH&3sGys1yDCbY99AIneBxq^nr% z%|x~}Ymge_&UBWA%b7G(Qd;_m_X7yN-RZxy^z_f9hI@LX(_Iw%-a) zv5MUy+qlf7?j8eoNqdtlF}gvN|lL8zL7F@cjKX7^vZIaF1thZw5;= zwcx*^sbu6+U+~&~@w)ocixW&qMWvjjh#=HDh72@fbMw65$VOojQ5~FaP)h-UoR`=2 z#`hkzCSag2u*TxVdwL1D(wq+Tx~2%;q&P(tlsphAyI*McuX$=Bc?XWKVWRp%kgs>8E0GgHvsfDZ{n^j- zA3uHoD3AG+&!Md&d@;>~%WOD^F1I`0faxAGvZMzb)%Pdc^hF7ghuV_2IBm!~p)}%j zo8mZCVkd_G27KkfY7U|jL~sdYh``g2gsHleCr~YRU*0be29P!0*{=s>JmMDa`l0Z> zzJt>$<)-U!L*()CQzfUa*K2$#VavxSHJ9a$`4T2)S%mB0z7nv>0)M zN&GRPpY`%KF$ly1YD!A)Y;0DSmysb*J0dGPxdt!eG023r-a7lt&&wg3ot=GoeheyR zP)glLCrL<6RZv!^!U+YD9DwnPJA6m!Hn;Ab)FE!J^e6lH_&`6wZ2tPC74wgw?h+i4 zJPPYrY?fESQp-viep>Rb`W}q{=T>g>SZjZpy|%loV4chMeG z(|!u+jO?|!aufx$`21`MR4<8L#`SsgWz5`pclt@`$wODiW4|CQ+54}hq*#-p^bmmvTL8WMj2u^0^EaL)#AkG!|fi~Ysb zW+pc=L*7LKJaLizb5^*7Qgwo<&JW?{=JepOpZM2bN+%y43BYm4yM0as;gT)_InBjsG>-#Y{@lV z05)50i8^Ce*$(f$&LpHtMP=piOtqFjB{Tew0N^ryjx(jIGVvmN;cQoFRN9_+I03HG?P*BV~+2PaVNQ;1DRJDOTvRz%__Uk$g9Jafx9L z-VER8I=)K9bT>7%gR2La0H9TBo2fsERjzc#13%Sqs7XZv*iIzQn-k^#4EhRqb;#>d zUe5481kG|%U8||ula}uJ^1B0((Jei}SyS<@MNK&kfLHh-cR%)0ZWgjNkq6D0eOD`B zbmJtt}#ci-+Wr(?e`kP3SGv9`iabNJspdSXD% zC$B*3=`O9AXs3fDq<{1y$EvRqX0}?)>+qk?9*zgce}P&zVes|^Th2qvmj`nNdTfP#7uBqb*Isz^l7_dft@(tn@; literal 0 HcmV?d00001 diff --git a/website/sidebars.js b/website/sidebars.js index 9d60a5811c..6ccfc42180 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -101,6 +101,7 @@ module.exports = { items: [ "admin_hosts_blender", "admin_hosts_hiero", + "admin_hosts_houdini", "admin_hosts_maya", "admin_hosts_nuke", "admin_hosts_resolve", @@ -146,7 +147,7 @@ module.exports = { ], }, ], - Dev: [ + Dev: [ "dev_introduction", "dev_requirements", "dev_build", @@ -160,5 +161,5 @@ module.exports = { "dev_publishing" ] } - ] + ] }; From b3517a2da945b72b6b13eff292fa7d6ed63861b0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 09:47:21 +0200 Subject: [PATCH 053/513] Remove print statement --- openpype/tools/loader/model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6cb9ba2c6d..d9b1c708e0 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -564,7 +564,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # This runs in the main thread because it involves the host DCC if self._host: time_since_refresh = time.time() - self._host_loaded_refresh_time - print(time_since_refresh) if time_since_refresh > self._host_loaded_refresh_timeout: repre_ids = {con.get("representation") for con in self._host.ls()} From ca40a71f5c33a1f557039661bbbdd8db5d22738b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 09:48:52 +0200 Subject: [PATCH 054/513] Reduce queries to get loaded subset ids --- openpype/tools/loader/model.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index d9b1c708e0..9d1f1e045c 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -483,24 +483,22 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version # Check loaded subsets - subsets_loaded_by_id = set() + loaded_subset_ids = set() ids = self._loaded_representation_ids if ids: if self._doc_fetching_stop: return - # Get subsets from representations + # Get subset ids from loaded representations in workfile # todo: optimize with aggregation query to distinct subset id representations = get_representations(project_name, representation_ids=ids, fields=["parent"]) - parents_by_repre_id = get_representations_parents( - project_name, - representations=representations - ) - for repre_parents in parents_by_repre_id.values(): - repre_subset = repre_parents[1] - subsets_loaded_by_id.add(repre_subset["_id"]) + version_ids = set(repre["parent"] for repre in representations) + versions = get_versions(project_name, + version_ids=version_ids, + fields=["parent"]) + loaded_subset_ids = set(version["parent"] for version in versions) if self._doc_fetching_stop: return @@ -528,7 +526,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "subset_families": subset_families, "last_versions_by_subset_id": last_versions_by_subset_id, "repre_info_by_version_id": repre_info, - "subsets_loaded_by_id": subsets_loaded_by_id + "subsets_loaded_by_id": loaded_subset_ids } self.doc_fetched.emit() From 5f0a8d700e5fc72570b70e56240b488819da3d43 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 11:22:10 +0200 Subject: [PATCH 055/513] Maya Redshift: Skip aov file format check for Cryptomatte --- .../plugins/publish/validate_rendersettings.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 1dab3274a0..feb6a16dac 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -180,14 +180,17 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): redshift_AOV_prefix )) invalid = True - # get aov format - aov_ext = cmds.getAttr( - "{}.fileFormat".format(aov), asString=True) - default_ext = cmds.getAttr( - "redshiftOptions.imageFormat", asString=True) + # check aov file format + aov_ext = cmds.getAttr("{}.fileFormat".format(aov)) + default_ext = cmds.getAttr("redshiftOptions.imageFormat") + aov_type = cmds.getAttr("{}.aovType".format(aov)) + if aov_type == "Cryptomatte": + # redshift Cryptomatte AOV always uses "Cryptomatte (EXR)" + # so we ignore validating file format for it. + pass - if default_ext != aov_ext: + elif default_ext != aov_ext: cls.log.error(("AOV file format is not the same " "as the one set globally " "{} != {}").format(default_ext, From 551f34a873c89e739dc0b5d28a74eeec3f79dac2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 14:25:29 +0200 Subject: [PATCH 056/513] Add subsetGroup column to scene inventory --- openpype/tools/sceneinventory/model.py | 11 +++++++++-- openpype/tools/sceneinventory/window.py | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 63fbe04c5c..97cc11ff23 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -34,7 +34,8 @@ from .lib import ( class InventoryModel(TreeModel): """The model for the inventory""" - Columns = ["Name", "version", "count", "family", "loader", "objectName"] + Columns = ["Name", "version", "count", "family", + "subsetGroup", "loader", "objectName"] OUTDATED_COLOR = QtGui.QColor(235, 30, 30) CHILD_OUTDATED_COLOR = QtGui.QColor(200, 160, 30) @@ -157,8 +158,13 @@ class InventoryModel(TreeModel): # Family icon return item.get("familyIcon", None) + column_name = self.Columns[index.column()] + + if column_name == "subsetGroup" and item.get("subsetGroup"): + return qtawesome.icon("fa.object-group", + color=get_default_entity_icon_color()) + if item.get("isGroupNode"): - column_name = self.Columns[index.column()] if column_name == "active_site": provider = item.get("active_site_provider") return self._site_icons.get(provider) @@ -423,6 +429,7 @@ class InventoryModel(TreeModel): group_node["familyIcon"] = family_icon group_node["count"] = len(group_items) group_node["isGroupNode"] = True + group_node["subsetGroup"] = subset["data"].get("subsetGroup") if self.sync_enabled: progress = get_progress_for_repre( diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py index 054c2a2daa..02addbccfe 100644 --- a/openpype/tools/sceneinventory/window.py +++ b/openpype/tools/sceneinventory/window.py @@ -88,7 +88,8 @@ class SceneInventoryWindow(QtWidgets.QDialog): view.setColumnWidth(1, 55) # version view.setColumnWidth(2, 55) # count view.setColumnWidth(3, 150) # family - view.setColumnWidth(4, 100) # namespace + view.setColumnWidth(4, 120) # subsetGroup + view.setColumnWidth(5, 150) # loader # apply delegates version_delegate = VersionDelegate(legacy_io, self) From ca424baf73db7d1df54d3faabacc032a9362b2c5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 16:39:20 +0200 Subject: [PATCH 057/513] Scene Inventory: Maya add actions to select from or to scene --- .../plugins/inventory/select_containers.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 openpype/hosts/maya/plugins/inventory/select_containers.py diff --git a/openpype/hosts/maya/plugins/inventory/select_containers.py b/openpype/hosts/maya/plugins/inventory/select_containers.py new file mode 100644 index 0000000000..4b7c92729f --- /dev/null +++ b/openpype/hosts/maya/plugins/inventory/select_containers.py @@ -0,0 +1,46 @@ +from maya import cmds + +from openpype.pipeline import InventoryAction, registered_host +from openpype.hosts.maya.api.lib import get_container_members + + +class SelectInScene(InventoryAction): + """Select nodes in the scene from selected containers in scene inventory""" + + label = "Select In Scene" + icon = "search" + color = "#888888" + order = 99 + + def process(self, containers): + + all_members = [] + for container in containers: + members = get_container_members(container) + all_members.extend(members) + cmds.select(all_members, replace=True, noExpand=True) + + +class SelectFromScene(InventoryAction): + """Select containers in scene inventory from the current scene selection""" + + label = "Select From Scene" + icon = "search" + color = "#888888" + order = 100 + + def process(self, containers): + + selection = set(cmds.ls(selection=True, long=True, objectsOnly=True)) + host = registered_host() + + to_select = [] + for container in host.ls(): + members = get_container_members(container) + if any(member in selection for member in members): + to_select.append(container["objectName"]) + + return { + "objectNames": to_select, + "options": {"clear": True} + } From a3ee45edd906aae36e09f1e40cf815d2c4f9605e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 18:14:23 +0200 Subject: [PATCH 058/513] Refactor host.ls() to host.get_containers() Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/plugins/inventory/select_containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/inventory/select_containers.py b/openpype/hosts/maya/plugins/inventory/select_containers.py index 4b7c92729f..13c2322bc0 100644 --- a/openpype/hosts/maya/plugins/inventory/select_containers.py +++ b/openpype/hosts/maya/plugins/inventory/select_containers.py @@ -35,7 +35,7 @@ class SelectFromScene(InventoryAction): host = registered_host() to_select = [] - for container in host.ls(): + for container in host.get_containers(): members = get_container_members(container) if any(member in selection for member in members): to_select.append(container["objectName"]) From 3252c732e1c41460016bc8a17ab01ea5ade34f60 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 12 Aug 2022 18:28:14 +0200 Subject: [PATCH 059/513] OP-3682 - added more fields to metadata Additional fields could be useful in the future for some addon Store or pulling information into customer's internal CRM. --- distribution/addon_distribution.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index e29e9bbf9b..465950f6e8 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -3,6 +3,7 @@ from enum import Enum from abc import abstractmethod import attr import logging +import requests from distribution.file_handler import RemoteFileHandler @@ -21,6 +22,9 @@ class AddonInfo(object): addon_url = attr.ib(default=None) type = attr.ib(default=None) hash = attr.ib(default=None) + description = attr.ib(default=None) + license = attr.ib(default=None) + authors = attr.ib(default=None) class AddonDownloader: From 6187cf18f50c43ca60f234bce439a4dc466263cf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 12 Aug 2022 19:08:30 +0200 Subject: [PATCH 060/513] OP-3682 - implemented basic GET Used publish Postman mock server for testing --- distribution/addon_distribution.py | 59 ++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index 465950f6e8..86b6de3a74 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -34,7 +34,7 @@ class AddonDownloader: self._downloaders = {} def register_format(self, downloader_type, downloader): - self._downloaders[downloader_type] = downloader + self._downloaders[downloader_type.value] = downloader def get_downloader(self, downloader_type): downloader = self._downloaders.get(downloader_type) @@ -115,24 +115,31 @@ class HTTPAddonDownloader(AddonDownloader): return os.path.join(destination, file_name) -def get_addons_info(): +def get_addons_info(server_endpoint): """Returns list of addon information from Server""" # TODO temp - addon_info = AddonInfo( - **{"name": "openpype_slack", - "version": "1.0.0", - "addon_url": "c:/projects/openpype_slack_1.0.0.zip", - "type": UrlType.OS, - "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658"}) # noqa + # addon_info = AddonInfo( + # **{"name": "openpype_slack", + # "version": "1.0.0", + # "addon_url": "c:/projects/openpype_slack_1.0.0.zip", + # "type": UrlType.OS, + # "hash": "4f6b8568eb9dd6f510fd7c4dcb676788"}) # noqa + # + # http_addon = AddonInfo( + # **{"name": "openpype_slack", + # "version": "1.0.0", + # "addon_url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing", # noqa + # "type": UrlType.HTTP, + # "hash": "4f6b8568eb9dd6f510fd7c4dcb676788"}) # noqa - http_addon = AddonInfo( - **{"name": "openpype_slack", - "version": "1.0.0", - "addon_url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing", # noqa - "type": UrlType.HTTP, - "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658"}) # noqa + response = requests.get(server_endpoint) + if not response.ok: + raise Exception(response.text) - return [http_addon] + addons_info = [] + for addon in response.json(): + addons_info.append(AddonInfo(**addon)) + return addons_info def update_addon_state(addon_infos, destination_folder, factory, @@ -167,15 +174,29 @@ def update_addon_state(addon_infos, destination_folder, factory, downloader.unzip(zip_file_path, addon_dest) except Exception: log.warning(f"Error happened during updating {addon.name}", - stack_info=True) + exc_info=True) + + +def check_addons(server_endpoint, addon_folder, downloaders): + """Main entry point to compare existing addons with those on server.""" + addons_info = get_addons_info(server_endpoint) + update_addon_state(addons_info, + addon_folder, + downloaders) def cli(args): - addon_folder = "c:/Users/petrk/AppData/Local/pypeclub/openpype/addons" + addon_folder = "c:/projects/testing_addons/pypeclub/openpype/addons" downloader_factory = AddonDownloader() downloader_factory.register_format(UrlType.OS, OSAddonDownloader) downloader_factory.register_format(UrlType.HTTP, HTTPAddonDownloader) - print(update_addon_state(get_addons_info(), addon_folder, - downloader_factory)) + test_endpoint = "https://34e99f0f-f987-4715-95e6-d2d88caa7586.mock.pstmn.io/get_addons_info" # noqa + if os.environ.get("OPENPYPE_SERVER"): # TODO or from keychain + server_endpoint = os.environ.get("OPENPYPE_SERVER") + "get_addons_info" + else: + server_endpoint = test_endpoint + + check_addons(server_endpoint, addon_folder, downloader_factory) + From 385b6b97f02c2a384e3432fd8f204ee4f6810e18 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 12 Aug 2022 19:09:06 +0200 Subject: [PATCH 061/513] OP-3682 - Hound --- distribution/addon_distribution.py | 1 - 1 file changed, 1 deletion(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index 86b6de3a74..a0c48923df 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -199,4 +199,3 @@ def cli(args): server_endpoint = test_endpoint check_addons(server_endpoint, addon_folder, downloader_factory) - From 5cb99f5209d7aff8b81982dcb5ba0b604f6557b1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Aug 2022 15:55:13 +0200 Subject: [PATCH 062/513] Move set render settings menu entry --- openpype/hosts/maya/api/menu.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index b7ab529a55..b4511571fb 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -99,13 +99,6 @@ def install(): cmds.menuItem(divider=True) - cmds.menuItem( - "Set Render Settings", - command=lambda *args: lib_rendersettings.RenderSettings().set_default_renderer_settings() # noqa - ) - - cmds.menuItem(divider=True) - cmds.menuItem( "Work Files...", command=lambda *args: host_tools.show_workfiles( @@ -127,6 +120,12 @@ def install(): "Set Colorspace", command=lambda *args: lib.set_colorspace(), ) + + cmds.menuItem( + "Set Render Settings", + command=lambda *args: lib_rendersettings.RenderSettings().set_default_renderer_settings() # noqa + ) + cmds.menuItem(divider=True, parent=MENU_NAME) cmds.menuItem( "Build First Workfile", From 4504078481da5cf0ebf7128cf7fdc3062d3c926e Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Mon, 15 Aug 2022 16:28:33 +0200 Subject: [PATCH 063/513] publisher collect current workfile as publishable representation --- .../plugins/publish/collect_current_file.py | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_current_file.py b/openpype/hosts/blender/plugins/publish/collect_current_file.py index 72976c490b..1ca28f67f6 100644 --- a/openpype/hosts/blender/plugins/publish/collect_current_file.py +++ b/openpype/hosts/blender/plugins/publish/collect_current_file.py @@ -1,6 +1,23 @@ +import os import bpy import pyblish.api +from openpype.pipeline import legacy_io +from openpype.hosts.blender.api import workio + + +class SaveWorkfiledAction(pyblish.api.Action): + """Save Workfile.""" + label = "Save Workfile" + on = "failed" + icon = "save" + + def process(self, context, plugin): + current_file = workio.current_file() + if current_file: + workio.save_file(current_file) + else: + bpy.ops.wm.avalon_workfiles() class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): @@ -8,12 +25,58 @@ class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.5 label = "Blender Current File" - hosts = ['blender'] + hosts = ["blender"] + actions = [SaveWorkfiledAction] def process(self, context): """Inject the current working file""" - current_file = bpy.data.filepath - context.data['currentFile'] = current_file + current_file = workio.current_file() + has_unsaved_changes = workio.has_unsaved_changes() - assert current_file != '', "Current file is empty. " \ + context.data["currentFile"] = current_file + + assert current_file, ( + "Current file is empty. Save the file before continuing." + ) + + assert not has_unsaved_changes, ( + "Current file has unsaved changes. " "Save the file before continuing." + ) + + folder, file = os.path.split(current_file) + filename, ext = os.path.splitext(file) + + task = legacy_io.Session["AVALON_TASK"] + + data = {} + + # create instance + instance = context.create_instance(name=filename) + subset = "workfile" + task.capitalize() + + data.update({ + "subset": subset, + "asset": os.getenv("AVALON_ASSET", None), + "label": subset, + "publish": True, + "family": "workfile", + "families": ["workfile"], + "setMembers": [current_file], + "frameStart": bpy.context.scene.frame_start, + "frameEnd": bpy.context.scene.frame_end, + }) + + data["representations"] = [{ + "name": ext.lstrip("."), + "ext": ext.lstrip("."), + "files": file, + "stagingDir": folder, + }] + + instance.data.update(data) + + self.log.info("Collected instance: {}".format(file)) + self.log.info("Scene path: {}".format(current_file)) + self.log.info("staging Dir: {}".format(folder)) + self.log.info("subset: {}".format(subset)) From 2cebaf718cc3c7df21a3543e90bac91ae56e68d8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 Aug 2022 21:04:51 +0200 Subject: [PATCH 064/513] global: moving collect audio to global --- .../plugins/publish/collect_audio.py | 113 ------------------ openpype/plugins/publish/collect_audio.py | 51 ++++++++ 2 files changed, 51 insertions(+), 113 deletions(-) delete mode 100644 openpype/hosts/celaction/plugins/publish/collect_audio.py create mode 100644 openpype/plugins/publish/collect_audio.py diff --git a/openpype/hosts/celaction/plugins/publish/collect_audio.py b/openpype/hosts/celaction/plugins/publish/collect_audio.py deleted file mode 100644 index c6e3bf2c03..0000000000 --- a/openpype/hosts/celaction/plugins/publish/collect_audio.py +++ /dev/null @@ -1,113 +0,0 @@ -import os -import collections -from pprint import pformat - -import pyblish.api - -from openpype.client import ( - get_subsets, - get_last_versions, - get_representations -) -from openpype.pipeline import legacy_io - - -class AppendCelactionAudio(pyblish.api.ContextPlugin): - - label = "Colect Audio for publishing" - order = pyblish.api.CollectorOrder + 0.1 - - def process(self, context): - self.log.info('Collecting Audio Data') - asset_doc = context.data["assetEntity"] - - # get all available representations - subsets = self.get_subsets( - asset_doc, - representations=["audio", "wav"] - ) - self.log.info(f"subsets is: {pformat(subsets)}") - - if not subsets.get("audioMain"): - raise AttributeError("`audioMain` subset does not exist") - - reprs = subsets.get("audioMain", {}).get("representations", []) - self.log.info(f"reprs is: {pformat(reprs)}") - - repr = next((r for r in reprs), None) - if not repr: - raise "Missing `audioMain` representation" - self.log.info(f"representation is: {repr}") - - audio_file = repr.get('data', {}).get('path', "") - - if os.path.exists(audio_file): - context.data["audioFile"] = audio_file - self.log.info( - 'audio_file: {}, has been added to context'.format(audio_file)) - else: - self.log.warning("Couldn't find any audio file on Ftrack.") - - def get_subsets(self, asset_doc, representations): - """ - Query subsets with filter on name. - - The method will return all found subsets and its defined version - and subsets. Version could be specified with number. Representation - can be filtered. - - Arguments: - asset_doct (dict): Asset (shot) mongo document - representations (list): list for all representations - - Returns: - dict: subsets with version and representations in keys - """ - - # Query all subsets for asset - project_name = legacy_io.active_project() - subset_docs = get_subsets( - project_name, asset_ids=[asset_doc["_id"]], fields=["_id"] - ) - # Collect all subset ids - subset_ids = [ - subset_doc["_id"] - for subset_doc in subset_docs - ] - - # Check if we found anything - assert subset_ids, ( - "No subsets found. Check correct filter. " - "Try this for start `r'.*'`: asset: `{}`" - ).format(asset_doc["name"]) - - last_versions_by_subset_id = get_last_versions( - project_name, subset_ids, fields=["_id", "parent"] - ) - - version_docs_by_id = {} - for version_doc in last_versions_by_subset_id.values(): - version_docs_by_id[version_doc["_id"]] = version_doc - - repre_docs = get_representations( - project_name, - version_ids=version_docs_by_id.keys(), - representation_names=representations - ) - repre_docs_by_version_id = collections.defaultdict(list) - for repre_doc in repre_docs: - version_id = repre_doc["parent"] - repre_docs_by_version_id[version_id].append(repre_doc) - - output_dict = {} - for version_id, repre_docs in repre_docs_by_version_id.items(): - version_doc = version_docs_by_id[version_id] - subset_id = version_doc["parent"] - subset_doc = last_versions_by_subset_id[subset_id] - # Store queried docs by subset name - output_dict[subset_doc["name"]] = { - "representations": repre_docs, - "version": version_doc - } - - return output_dict diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py new file mode 100644 index 0000000000..022334e0f3 --- /dev/null +++ b/openpype/plugins/publish/collect_audio.py @@ -0,0 +1,51 @@ +import pyblish.api +from pprint import pformat + +from openpype.client import ( + get_last_version_by_subset_name, + get_representations, +) +from openpype.pipeline import ( + legacy_io, + get_representation_path, +) + + +class CollectAudio(pyblish.api.InstancePlugin): + + label = "Colect Audio" + order = pyblish.api.CollectorOrder + 0.1 + hosts = ["standalonepublisher"] + + def process(self, instance): + self.log.info('Collecting Audio Data') + + project_name = legacy_io.active_project() + asset_name = instance.data["asset"] + # * Add audio to instance if exists. + # Find latest versions document + last_version_doc = get_last_version_by_subset_name( + project_name, "audioMain", asset_name=asset_name, fields=["_id"] + ) + + repre_doc = None + if last_version_doc: + # Try to find it's representation (Expected there is only one) + repre_docs = list(get_representations( + project_name, version_ids=[last_version_doc["_id"]] + )) + if not repre_docs: + self.log.warning( + "Version document does not contain any representations" + ) + else: + repre_doc = repre_docs[0] + + # Add audio to instance if representation was found + if repre_doc: + instance.data["audio"] = [{ + "offset": 0, + "filename": get_representation_path(repre_doc) + }] + + self.log.debug("instance.data: {}".format(pformat(instance.data))) From 7c06a1fe8cbbdd612fb9a6a8f6b8df092f8e810a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 Aug 2022 21:06:40 +0200 Subject: [PATCH 065/513] global: improving hosts and families in collect audio --- openpype/plugins/publish/collect_audio.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 022334e0f3..f4cad86f94 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -15,7 +15,24 @@ class CollectAudio(pyblish.api.InstancePlugin): label = "Colect Audio" order = pyblish.api.CollectorOrder + 0.1 - hosts = ["standalonepublisher"] + families = ["review"] + hosts = [ + "nuke", + "maya", + "shell", + "hiero", + "premiere", + "harmony", + "traypublisher", + "standalonepublisher", + "fusion", + "tvpaint", + "resolve", + "webpublisher", + "aftereffects", + "flame", + "unreal" + ] def process(self, instance): self.log.info('Collecting Audio Data') From 495a65707cfa7384dcd1498863a34942cb85a5ac Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 15 Aug 2022 21:13:09 +0200 Subject: [PATCH 066/513] Global: improving docstring and comments --- openpype/plugins/publish/collect_audio.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index f4cad86f94..4ba47f739d 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -12,7 +12,9 @@ from openpype.pipeline import ( class CollectAudio(pyblish.api.InstancePlugin): + """ Collecting available audio subset to instance + """ label = "Colect Audio" order = pyblish.api.CollectorOrder + 0.1 families = ["review"] @@ -35,11 +37,12 @@ class CollectAudio(pyblish.api.InstancePlugin): ] def process(self, instance): + # * Add audio to instance if exists. self.log.info('Collecting Audio Data') project_name = legacy_io.active_project() asset_name = instance.data["asset"] - # * Add audio to instance if exists. + # Find latest versions document last_version_doc = get_last_version_by_subset_name( project_name, "audioMain", asset_name=asset_name, fields=["_id"] From ba3e20a712c8d91e3d1c68dd1d2547bcffad08eb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Aug 2022 10:48:51 +0200 Subject: [PATCH 067/513] Don't skip empty value - all keys are considered required attributes --- openpype/hosts/maya/api/pipeline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index f565f6a308..34138e64bd 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -349,8 +349,6 @@ def containerise(name, ] for key, value in data: - if not value: - continue if isinstance(value, (int, float)): cmds.addAttr(container, longName=key, attributeType="short") From b7d4a2a747ff572c091ca7f6d50b01911d570439 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Aug 2022 10:49:50 +0200 Subject: [PATCH 068/513] Simplify logic since all values should be strings --- openpype/hosts/maya/api/pipeline.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 34138e64bd..d4067ea659 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -349,14 +349,8 @@ def containerise(name, ] for key, value in data: - - if isinstance(value, (int, float)): - cmds.addAttr(container, longName=key, attributeType="short") - cmds.setAttr(container + "." + key, value) - - else: - cmds.addAttr(container, longName=key, dataType="string") - cmds.setAttr(container + "." + key, value, type="string") + cmds.addAttr(container, longName=key, dataType="string") + cmds.setAttr(container + "." + key, value, type="string") main_container = cmds.ls(AVALON_CONTAINERS, type="objectSet") if not main_container: From e48eb3ba47f9afc41ce3c4c06b6e7ffb36746f89 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 11:15:01 +0200 Subject: [PATCH 069/513] remove create_shelf function since it is no longer needed --- openpype/hosts/houdini/api/lib.py | 32 ------------------------------- 1 file changed, 32 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 55832abeb3..c8a7f92bb9 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -460,35 +460,3 @@ def reset_framerange(): hou.playbar.setFrameRange(frame_start, frame_end) hou.playbar.setPlaybackRange(frame_start, frame_end) hou.setFrame(frame_start) - - -def create_shelf(): - hou.shelves.beginChangeBlock() - - custom_shelf = hou.shelves.newShelf( - file_path='', - name="custom_shelf", - label="Custom Shelf" - ) - - new_tool = hou.shelves.newTool( - file_path='', - name='new_tool', - label='New Tool', - script='', - language=hou.scriptLanguage.Python, - icon='', - help='This is a new tool' - ) - - if new_tool not in custom_shelf.tools(): - custom_shelf.setTools(list(custom_shelf.tools()) + [new_tool]) - - shelf_set = [ - shelf for shelf in hou.shelves.shelfSets().values() - if shelf.label() == "Create and Refine" - ][0] - - shelf_set.setShelves(shelf_set.shelves() + (custom_shelf,)) - - hou.shelves.endChangeBlock() From c9f60bb848b81f9b4c095281cfae3c3d27e8d652 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 11:19:50 +0200 Subject: [PATCH 070/513] remove invalid default values --- .../defaults/project_settings/houdini.json | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 78e0d595cf..43d2ad132a 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -5,21 +5,8 @@ "shelf_set_source_path": { "windows": "", "darwin": "", - "linux": "/path/to/your/shelf_set_file" - }, - "shelf_definition": [ - { - "shelf_name": "OpenPype Shelf", - "tools_list": [ - { - "label": "OpenPype Tool", - "script": "/path/to/your/tool_script", - "icon": "/path/to/your/icon", - "help": "Help message for your tool" - } - ] - } - ] + "linux": "" + } } ], "create": { From 9c7bcb84aa42a2f2c083c856ae421a9d264f32dc Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 12:25:05 +0200 Subject: [PATCH 071/513] fix typo and tool creation --- openpype/hosts/houdini/api/shelves.py | 33 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index bb92aa828e..d9a3a34da6 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -10,7 +10,7 @@ log = logging.getLogger("openpype.hosts.houdini") def generate_shelves(): - """This function generates complete shelves from shef set to tools + """This function generates complete shelves from shelf set to tools in Houdini from openpype project settings houdini shelf definition. Raises: @@ -23,8 +23,8 @@ def generate_shelves(): shelves_set_config = project_settings["houdini"]["shelves"] if not shelves_set_config: - log.warning( - "SHELF WARNGING: No custom shelves found in project settings." + log.info( + "SHELF INFO: No custom shelves found in project settings." ) return @@ -45,7 +45,7 @@ def generate_shelves(): shelf_set_name = shelf_set_config.get('shelf_set_name') if not shelf_set_name: log.warning( - "SHELF WARNGING: No name found in shelf set definition." + "SHELF WARNING: No name found in shelf set definition." ) return @@ -54,8 +54,8 @@ def generate_shelves(): shelves_definition = shelf_set_config.get('shelf_definition') if not shelves_definition: - log.warning( - "SHELF WARNING: \ + log.info( + "SHELF INFO: \ No shelf definition found for shelf set named '{}'".format(shelf_set_name) ) return @@ -64,26 +64,34 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf_name = shelf_definition.get('shelf_name') if not shelf_name: log.warning( - "SHELF WARNGING: No name found in shelf definition." + "SHELF WARNING: No name found in shelf definition." ) return shelf = get_or_create_shelf(shelf_name) + if not shelf_definition.get('tools_list'): + log.warning("TOOLS INFO: No tool definition found for \ +shelf named {}".format(shelf_name)) + return + + mandatory_attributes = ['name', 'script'] for tool_definition in shelf_definition.get('tools_list'): # We verify that the name and script attibutes of the tool # are set - mandatory_attributes = ['name', 'script'] if not all( [v for k, v in tool_definition.items() if k in mandatory_attributes] ): log.warning("TOOLS ERROR: You need to specify at least \ the name and the script path of the tool.") - return + continue tool = get_or_create_tool(tool_definition, shelf) + if not tool: + return + # Add the tool to the shelf if not already in it if tool not in shelf.tools(): shelf.setTools(list(shelf.tools()) + [tool]) @@ -105,12 +113,12 @@ def get_or_create_shelf_set(shelf_set_label): """ all_shelves_sets = hou.shelves.shelfSets().values() - shelf_set = [ + shelf_sets = [ shelf for shelf in all_shelves_sets if shelf.label() == shelf_set_label ] - if shelf_set: - return shelf_set[0] + if shelf_sets: + return shelf_sets[0] shelf_set_name = shelf_set_label.replace(' ', '_').lower() new_shelf_set = hou.shelves.newShelfSet( @@ -170,6 +178,7 @@ def get_or_create_tool(tool_definition, shelf): return existing_tool[0] tool_name = tool_label.replace(' ', '_').lower() + log.warning(tool_definition) if not os.path.exists(tool_definition['script']): log.warning( From a556f2393276048e4eaeeeeb11b82d5df9fbab59 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Aug 2022 12:33:39 +0200 Subject: [PATCH 072/513] Force `str` type for the values Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index d4067ea659..84963c55a4 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -350,7 +350,7 @@ def containerise(name, for key, value in data: cmds.addAttr(container, longName=key, dataType="string") - cmds.setAttr(container + "." + key, value, type="string") + cmds.setAttr(container + "." + key, str(value), type="string") main_container = cmds.ls(AVALON_CONTAINERS, type="objectSet") if not main_container: From 64ce18b0e49b4fdefafa93ddc2508a3ca424e9b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Aug 2022 12:34:29 +0200 Subject: [PATCH 073/513] Remove redundant `str` conversion since that's now done after --- openpype/hosts/maya/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 84963c55a4..2ee8fb8e5d 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -344,7 +344,7 @@ def containerise(name, ("id", AVALON_CONTAINER_ID), ("name", name), ("namespace", namespace), - ("loader", str(loader)), + ("loader", loader), ("representation", context["representation"]["_id"]), ] From 538513304e9b3ebcd433765881a620a0e00bc48c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Aug 2022 13:20:25 +0200 Subject: [PATCH 074/513] OP-3682 - refactored OS to FILESYSTEM --- distribution/addon_distribution.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index a0c48923df..3cc2374b93 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -11,7 +11,7 @@ from distribution.file_handler import RemoteFileHandler class UrlType(Enum): HTTP = "http" GIT = "git" - OS = "os" + FILESYSTEM = "filesystem" @attr.s @@ -122,7 +122,7 @@ def get_addons_info(server_endpoint): # **{"name": "openpype_slack", # "version": "1.0.0", # "addon_url": "c:/projects/openpype_slack_1.0.0.zip", - # "type": UrlType.OS, + # "type": UrlType.FILESYSTEM, # "hash": "4f6b8568eb9dd6f510fd7c4dcb676788"}) # noqa # # http_addon = AddonInfo( @@ -189,7 +189,7 @@ def cli(args): addon_folder = "c:/projects/testing_addons/pypeclub/openpype/addons" downloader_factory = AddonDownloader() - downloader_factory.register_format(UrlType.OS, OSAddonDownloader) + downloader_factory.register_format(UrlType.FILESYSTEM, OSAddonDownloader) downloader_factory.register_format(UrlType.HTTP, HTTPAddonDownloader) test_endpoint = "https://34e99f0f-f987-4715-95e6-d2d88caa7586.mock.pstmn.io/get_addons_info" # noqa From f5d7634e007d4e9a27f76b7abb693daa7b9ba055 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 14:15:08 +0200 Subject: [PATCH 075/513] change tools mandatory attributes to set type and iterate only on those attributes --- openpype/hosts/houdini/api/shelves.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index d9a3a34da6..498fffc7cd 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -75,13 +75,12 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf named {}".format(shelf_name)) return - mandatory_attributes = ['name', 'script'] + mandatory_attributes = {'name', 'script'} for tool_definition in shelf_definition.get('tools_list'): # We verify that the name and script attibutes of the tool # are set if not all( - [v for k, v in tool_definition.items() if - k in mandatory_attributes] + tool_definition[key] for key in mandatory_attributes ): log.warning("TOOLS ERROR: You need to specify at least \ the name and the script path of the tool.") From e0abb7245c231d2cabec782b1172b9257fd096da Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 14:34:15 +0200 Subject: [PATCH 076/513] fix type and docstring style to match OpenPype's --- openpype/hosts/houdini/api/pipeline.py | 1 - openpype/hosts/houdini/api/shelves.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index f809f0ce56..d7a8135d86 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -310,7 +310,6 @@ def _set_context_settings(): fps resolution renderer - shelves Returns: None diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 498fffc7cd..725d162980 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -105,7 +105,7 @@ def get_or_create_shelf_set(shelf_set_label): creates a new shelf set. Arguments: - shelf_set_label {str} -- The label of the shelf set + shelf_set_label (str) -- The label of the shelf set Returns: hou.ShelfSet -- The shelf set existing or the new one @@ -153,7 +153,7 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - """This function verifies if the tool exsist and update it. If not, creates + """This function verifies if the tool exsists and updates it. If not, creates a new one. Arguments: @@ -177,7 +177,6 @@ def get_or_create_tool(tool_definition, shelf): return existing_tool[0] tool_name = tool_label.replace(' ', '_').lower() - log.warning(tool_definition) if not os.path.exists(tool_definition['script']): log.warning( From 129a38ebc0204fc9d6777a1b876d1f882fc929f4 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 14:34:15 +0200 Subject: [PATCH 077/513] fix type and docstring style to match OpenPype's --- openpype/hosts/houdini/api/pipeline.py | 1 - openpype/hosts/houdini/api/shelves.py | 17 ++++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index f809f0ce56..d7a8135d86 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -310,7 +310,6 @@ def _set_context_settings(): fps resolution renderer - shelves Returns: None diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 498fffc7cd..ba3fcc2af9 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -105,10 +105,10 @@ def get_or_create_shelf_set(shelf_set_label): creates a new shelf set. Arguments: - shelf_set_label {str} -- The label of the shelf set + shelf_set_label (str): The label of the shelf set Returns: - hou.ShelfSet -- The shelf set existing or the new one + hou.ShelfSet: The shelf set existing or the new one """ all_shelves_sets = hou.shelves.shelfSets().values() @@ -132,10 +132,10 @@ def get_or_create_shelf(shelf_label): a new shelf. Arguments: - shelf_label {str} -- The label of the shelf + shelf_label (str): The label of the shelf Returns: - hou.Shelf -- The shelf existing or the new one + hou.Shelf: The shelf existing or the new one """ all_shelves = hou.shelves.shelves().values() @@ -153,15 +153,15 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - """This function verifies if the tool exsist and update it. If not, creates + """This function verifies if the tool exsists and updates it. If not, creates a new one. Arguments: - tool_definition {dict} -- Dict with label, script, icon and help - shelf {hou.Shelf} -- The parent shelf of the tool + tool_definition (dict): Dict with label, script, icon and help + shelf (hou.Shelf): The parent shelf of the tool Returns: - hou.Tool -- The tool updated or the new one + hou.Tool: The tool updated or the new one """ existing_tools = shelf.tools() tool_label = tool_definition.get('label') @@ -177,7 +177,6 @@ def get_or_create_tool(tool_definition, shelf): return existing_tool[0] tool_name = tool_label.replace(' ', '_').lower() - log.warning(tool_definition) if not os.path.exists(tool_definition['script']): log.warning( From 74934a51b9ce7d581473426ef9206fad1ab4b486 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 16 Aug 2022 17:21:37 +0200 Subject: [PATCH 078/513] Nuke: removing audio inclusion from precollect write --- .../nuke/plugins/publish/precollect_writes.py | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index e37cc8a80a..17c4bc30cf 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -201,34 +201,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if not instance.data["review"]: instance.data["useSequenceForReview"] = False - project_name = legacy_io.active_project() - asset_name = instance.data["asset"] - # * Add audio to instance if exists. - # Find latest versions document - last_version_doc = get_last_version_by_subset_name( - project_name, "audioMain", asset_name=asset_name, fields=["_id"] - ) - - repre_doc = None - if last_version_doc: - # Try to find it's representation (Expected there is only one) - repre_docs = list(get_representations( - project_name, version_ids=[last_version_doc["_id"]] - )) - if not repre_docs: - self.log.warning( - "Version document does not contain any representations" - ) - else: - repre_doc = repre_docs[0] - - # Add audio to instance if representation was found - if repre_doc: - instance.data["audio"] = [{ - "offset": 0, - "filename": get_representation_path(repre_doc) - }] - self.log.debug("instance.data: {}".format(pformat(instance.data))) def is_prerender(self, families): From 737edadfd5166eb20197d818eefe2a1a22041b0d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 16 Aug 2022 17:23:42 +0200 Subject: [PATCH 079/513] global: preparation for settings attribute --- openpype/plugins/publish/collect_audio.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 4ba47f739d..7e3b42f375 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -36,6 +36,8 @@ class CollectAudio(pyblish.api.InstancePlugin): "unreal" ] + audio_subset_name = "audioMain" + def process(self, instance): # * Add audio to instance if exists. self.log.info('Collecting Audio Data') @@ -45,7 +47,10 @@ class CollectAudio(pyblish.api.InstancePlugin): # Find latest versions document last_version_doc = get_last_version_by_subset_name( - project_name, "audioMain", asset_name=asset_name, fields=["_id"] + project_name, + self.audio_subset_name, + asset_name=asset_name, + fields=["_id"] ) repre_doc = None From 266975d6942156e71565a3847e4e62a9490dee71 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 16 Aug 2022 17:36:39 +0200 Subject: [PATCH 080/513] settings: adding collect audio plugin --- .../defaults/project_settings/global.json | 4 ++++ .../schemas/schema_global_publish.json | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0ff9363ba7..9258343440 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -3,6 +3,10 @@ "CollectAnatomyInstanceData": { "follow_workfile_version": false }, + "CollectAudio": { + "enabled": true, + "audio_subset_name": "audioMain" + }, "CollectSceneVersion": { "hosts": [ "aftereffects", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index e1aa230b49..2efee92832 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -18,6 +18,27 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "CollectAudio", + "label": "Collect Audio", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "audio_subset_name", + "label": "Name of audio variant", + "type": "text", + "placeholder": "audioMain" + } + ] + }, { "type": "dict", "collapsible": true, From 4d61eec9952b61331c32b89d66fae35de26d13b4 Mon Sep 17 00:00:00 2001 From: Thomas Fricard <51854004+friquette@users.noreply.github.com> Date: Wed, 17 Aug 2022 10:12:43 +0200 Subject: [PATCH 081/513] fix typo Co-authored-by: Roy Nieterau --- openpype/hosts/houdini/api/shelves.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index ba3fcc2af9..a802d70457 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -153,7 +153,7 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - """This function verifies if the tool exsists and updates it. If not, creates + """This function verifies if the tool exists and updates it. If not, creates a new one. Arguments: From e364c025e6b3b29e6c0dfecf8a029e42bb83d0fb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Aug 2022 10:34:58 +0200 Subject: [PATCH 082/513] Tweak labels for clearer distinction between the two actions --- openpype/hosts/maya/plugins/inventory/select_containers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/inventory/select_containers.py b/openpype/hosts/maya/plugins/inventory/select_containers.py index 4b7c92729f..d4a7ff401d 100644 --- a/openpype/hosts/maya/plugins/inventory/select_containers.py +++ b/openpype/hosts/maya/plugins/inventory/select_containers.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import get_container_members class SelectInScene(InventoryAction): """Select nodes in the scene from selected containers in scene inventory""" - label = "Select In Scene" + label = "Select in scene" icon = "search" color = "#888888" order = 99 @@ -21,10 +21,10 @@ class SelectInScene(InventoryAction): cmds.select(all_members, replace=True, noExpand=True) -class SelectFromScene(InventoryAction): +class HighlightBySceneSelection(InventoryAction): """Select containers in scene inventory from the current scene selection""" - label = "Select From Scene" + label = "Highlight by scene selection" icon = "search" color = "#888888" order = 100 From ee4ad799902f313a98ac1e4ab1403617e2d7d4bf Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 17 Aug 2022 11:48:29 +0200 Subject: [PATCH 083/513] change logs messages --- openpype/hosts/houdini/api/shelves.py | 35 +++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index ba3fcc2af9..805ce4c397 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -6,7 +6,7 @@ from openpype.settings import get_project_settings import hou -log = logging.getLogger("openpype.hosts.houdini") +log = logging.getLogger("openpype.hosts.houdini.shelves") def generate_shelves(): @@ -23,8 +23,8 @@ def generate_shelves(): shelves_set_config = project_settings["houdini"]["shelves"] if not shelves_set_config: - log.info( - "SHELF INFO: No custom shelves found in project settings." + log.debug( + "No custom shelves found in project settings." ) return @@ -34,7 +34,7 @@ def generate_shelves(): if shelf_set_filepath[current_os]: if not os.path.isfile(shelf_set_filepath[current_os]): raise FileNotFoundError( - "SHELF ERROR: This path doesn't exist - {}".format( + "This path doesn't exist - {}".format( shelf_set_filepath[current_os] ) ) @@ -45,7 +45,7 @@ def generate_shelves(): shelf_set_name = shelf_set_config.get('shelf_set_name') if not shelf_set_name: log.warning( - "SHELF WARNING: No name found in shelf set definition." + "No name found in shelf set definition." ) return @@ -54,9 +54,10 @@ def generate_shelves(): shelves_definition = shelf_set_config.get('shelf_definition') if not shelves_definition: - log.info( - "SHELF INFO: \ -No shelf definition found for shelf set named '{}'".format(shelf_set_name) + log.debug( + "No shelf definition found for shelf set named '{}'".format( + shelf_set_name + ) ) return @@ -64,15 +65,18 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf_name = shelf_definition.get('shelf_name') if not shelf_name: log.warning( - "SHELF WARNING: No name found in shelf definition." + "No name found in shelf definition." ) return shelf = get_or_create_shelf(shelf_name) if not shelf_definition.get('tools_list'): - log.warning("TOOLS INFO: No tool definition found for \ -shelf named {}".format(shelf_name)) + log.debug( + "No tool definition found for shelf named {}".format( + shelf_name + ) + ) return mandatory_attributes = {'name', 'script'} @@ -82,8 +86,9 @@ shelf named {}".format(shelf_name)) if not all( tool_definition[key] for key in mandatory_attributes ): - log.warning("TOOLS ERROR: You need to specify at least \ -the name and the script path of the tool.") + log.warning( + "You need to specify at least the name and \ +the script path of the tool.") continue tool = get_or_create_tool(tool_definition, shelf) @@ -153,7 +158,7 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - """This function verifies if the tool exsists and updates it. If not, creates + """This function verifies if the tool exists and updates it. If not, creates a new one. Arguments: @@ -180,7 +185,7 @@ def get_or_create_tool(tool_definition, shelf): if not os.path.exists(tool_definition['script']): log.warning( - "TOOL ERROR: This path doesn't exist - {}".format( + "This path doesn't exist - {}".format( tool_definition['script'] ) ) From 98ac7f538e6482007115aff917cdf4ccd39fbc83 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 Aug 2022 11:59:15 +0200 Subject: [PATCH 084/513] condition for case where audio is already collected --- openpype/plugins/publish/collect_audio.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 7e3b42f375..6aed3f82fe 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -68,9 +68,10 @@ class CollectAudio(pyblish.api.InstancePlugin): # Add audio to instance if representation was found if repre_doc: - instance.data["audio"] = [{ - "offset": 0, - "filename": get_representation_path(repre_doc) - }] + if not instance.data.get("audio"): + instance.data["audio"] = [{ + "offset": 0, + "filename": get_representation_path(repre_doc) + }] self.log.debug("instance.data: {}".format(pformat(instance.data))) From ed2aedd0feec05d7d53f2e62789f77838a6f2f47 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 18 Aug 2022 10:17:02 +0200 Subject: [PATCH 085/513] processing PR comments --- openpype/plugins/publish/collect_audio.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 6aed3f82fe..cf074392ee 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -15,7 +15,7 @@ class CollectAudio(pyblish.api.InstancePlugin): """ Collecting available audio subset to instance """ - label = "Colect Audio" + label = "Collect Audio" order = pyblish.api.CollectorOrder + 0.1 families = ["review"] hosts = [ @@ -39,8 +39,14 @@ class CollectAudio(pyblish.api.InstancePlugin): audio_subset_name = "audioMain" def process(self, instance): - # * Add audio to instance if exists. - self.log.info('Collecting Audio Data') + if instance.data.get("audio"): + self.log.info( + "Skipping Audio collecion. It is already collected" + ) + return + + # Add audio to instance if exists. + self.log.info('Collecting Audio Data ...') project_name = legacy_io.active_project() asset_name = instance.data["asset"] @@ -68,10 +74,10 @@ class CollectAudio(pyblish.api.InstancePlugin): # Add audio to instance if representation was found if repre_doc: - if not instance.data.get("audio"): - instance.data["audio"] = [{ - "offset": 0, - "filename": get_representation_path(repre_doc) - }] + instance.data["audio"] = [{ + "offset": 0, + "filename": get_representation_path(repre_doc) + }] + self.log.info("Audio Data added to instance ...") self.log.debug("instance.data: {}".format(pformat(instance.data))) From 694a07579287b12b5fcce35fc4a405700ca3f64c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Aug 2022 11:45:01 +0200 Subject: [PATCH 086/513] Refactor `subsetGroup` column name to `group` Co-authored-by: Milan Kolar --- openpype/tools/sceneinventory/model.py | 6 +++--- openpype/tools/sceneinventory/window.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 97cc11ff23..1a3b7c7055 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -35,7 +35,7 @@ class InventoryModel(TreeModel): """The model for the inventory""" Columns = ["Name", "version", "count", "family", - "subsetGroup", "loader", "objectName"] + "group", "loader", "objectName"] OUTDATED_COLOR = QtGui.QColor(235, 30, 30) CHILD_OUTDATED_COLOR = QtGui.QColor(200, 160, 30) @@ -160,7 +160,7 @@ class InventoryModel(TreeModel): column_name = self.Columns[index.column()] - if column_name == "subsetGroup" and item.get("subsetGroup"): + if column_name == "group" and item.get("group"): return qtawesome.icon("fa.object-group", color=get_default_entity_icon_color()) @@ -429,7 +429,7 @@ class InventoryModel(TreeModel): group_node["familyIcon"] = family_icon group_node["count"] = len(group_items) group_node["isGroupNode"] = True - group_node["subsetGroup"] = subset["data"].get("subsetGroup") + group_node["group"] = subset["data"].get("subsetGroup") if self.sync_enabled: progress = get_progress_for_repre( diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py index 02addbccfe..1f4585b650 100644 --- a/openpype/tools/sceneinventory/window.py +++ b/openpype/tools/sceneinventory/window.py @@ -88,7 +88,7 @@ class SceneInventoryWindow(QtWidgets.QDialog): view.setColumnWidth(1, 55) # version view.setColumnWidth(2, 55) # count view.setColumnWidth(3, 150) # family - view.setColumnWidth(4, 120) # subsetGroup + view.setColumnWidth(4, 120) # group view.setColumnWidth(5, 150) # loader # apply delegates From 149b65b8b05750966ab5d93a5751e7085cf81652 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Aug 2022 16:14:38 +0200 Subject: [PATCH 087/513] global: audio PR comments --- openpype/plugins/publish/collect_audio.py | 45 +++++++++++++++++------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index cf074392ee..3a765d345d 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -12,10 +12,12 @@ from openpype.pipeline import ( class CollectAudio(pyblish.api.InstancePlugin): - """ Collecting available audio subset to instance + """Collect asset's last published audio. + The audio subset name searched for is defined in: + project settings > Collect Audio """ - label = "Collect Audio" + label = "Collect Asset Audio" order = pyblish.api.CollectorOrder + 0.1 families = ["review"] hosts = [ @@ -46,10 +48,33 @@ class CollectAudio(pyblish.api.InstancePlugin): return # Add audio to instance if exists. - self.log.info('Collecting Audio Data ...') + self.log.info(( + "Searching for audio subset '{subset}'" + " in asset '{asset}'" + ).format( + subset=self.audio_subset_name, + asset=instance.data["asset"] + )) + + repre_doc = self._get_repre_doc(instance) + + # Add audio to instance if representation was found + if repre_doc: + instance.data["audio"] = [{ + "offset": 0, + "filename": get_representation_path(repre_doc) + }] + self.log.info("Audio Data added to instance ...") + + def _get_repre_doc(self, instance): + cache = instance.context.data.get("__cache_asset_audio", {}) + asset_name = instance.data["asset"] + + # first try to get it from cache + if asset_name in cache: + return cache[asset_name] project_name = legacy_io.active_project() - asset_name = instance.data["asset"] # Find latest versions document last_version_doc = get_last_version_by_subset_name( @@ -72,12 +97,8 @@ class CollectAudio(pyblish.api.InstancePlugin): else: repre_doc = repre_docs[0] - # Add audio to instance if representation was found - if repre_doc: - instance.data["audio"] = [{ - "offset": 0, - "filename": get_representation_path(repre_doc) - }] - self.log.info("Audio Data added to instance ...") + # update cache + cache[asset_name] = repre_doc + instance.context.data["__cache_asset_audio"].update(cache) - self.log.debug("instance.data: {}".format(pformat(instance.data))) + return repre_doc From d2d90ed2e098587cd466243f5d666093ed1db55f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Aug 2022 17:30:43 +0200 Subject: [PATCH 088/513] hound catch --- openpype/plugins/publish/collect_audio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 3a765d345d..e2fb766ec4 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -1,5 +1,4 @@ import pyblish.api -from pprint import pformat from openpype.client import ( get_last_version_by_subset_name, From f9182cb0f9979179f4b0647e915d630e635b81e7 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Tue, 23 Aug 2022 17:58:19 +0200 Subject: [PATCH 089/513] remove unsaved changes check --- .../blender/plugins/publish/collect_current_file.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/collect_current_file.py b/openpype/hosts/blender/plugins/publish/collect_current_file.py index 1ca28f67f6..c3097a0694 100644 --- a/openpype/hosts/blender/plugins/publish/collect_current_file.py +++ b/openpype/hosts/blender/plugins/publish/collect_current_file.py @@ -13,11 +13,7 @@ class SaveWorkfiledAction(pyblish.api.Action): icon = "save" def process(self, context, plugin): - current_file = workio.current_file() - if current_file: - workio.save_file(current_file) - else: - bpy.ops.wm.avalon_workfiles() + bpy.ops.wm.avalon_workfiles() class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): @@ -31,7 +27,6 @@ class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" current_file = workio.current_file() - has_unsaved_changes = workio.has_unsaved_changes() context.data["currentFile"] = current_file @@ -39,11 +34,6 @@ class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): "Current file is empty. Save the file before continuing." ) - assert not has_unsaved_changes, ( - "Current file has unsaved changes. " - "Save the file before continuing." - ) - folder, file = os.path.split(current_file) filename, ext = os.path.splitext(file) From 65d785d100c986128a88f1dc2c77b5321b85d7da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 24 Aug 2022 00:29:08 +0200 Subject: [PATCH 090/513] Draft stash for refactoring maya submit deadline to use `AbstractSubmitDeadline` base. - This does *NOT* work currently! --- .../deadline/abstract_submit_deadline.py | 22 + .../plugins/publish/submit_maya_deadline.py | 1297 ++++++++--------- 2 files changed, 623 insertions(+), 696 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 0bad981fdf..577378335e 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -359,6 +359,27 @@ class DeadlineJobInfo(object): def OutputDirectory(self, val): # noqa: N802 self._outputDirectory.append(val) + # Asset Dependency + # ---------------------------------------------- + _assetDependency = attr.ib(factory=list) + + @property + def AssetDependency(self): # noqa: N802 + """Return all OutputDirectory values formatted for Deadline. + + Returns: + dict: as `{'OutputDirectory0': 'dir'}` + + """ + out = {} + for index, v in enumerate(self._assetDependency): + out["AssetDependency{}".format(index)] = v + return out + + @OutputDirectory.setter + def AssetDependency(self, val): # noqa: N802 + self._assetDependency.append(val) + # Tile Job # ---------------------------------------------- TileJob = attr.ib(default=None) # Default: false @@ -396,6 +417,7 @@ class DeadlineJobInfo(object): serialized.update(self.OutputFilename) serialized.update(self.OutputFilenameTile) serialized.update(self.OutputDirectory) + serialized.update(self.AssetDependency) return serialized diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 7966861358..6dfa48a9f8 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -18,7 +18,6 @@ Attributes: from __future__ import print_function import os -import json import getpass import copy import re @@ -27,252 +26,32 @@ from datetime import datetime import itertools from collections import OrderedDict +import attr import clique -import requests from maya import cmds -import pyblish.api - -from openpype.lib import requests_post from openpype.hosts.maya.api import lib from openpype.pipeline import legacy_io -# Documentation for keys available at: -# https://docs.thinkboxsoftware.com -# /products/deadline/8.0/1_User%20Manual/manual -# /manual-submission.html#job-info-file-options - -payload_skeleton_template = { - "JobInfo": { - "BatchName": None, # Top-level group name - "Name": None, # Job name, as seen in Monitor - "UserName": None, - "Plugin": "MayaBatch", - "Frames": "{start}-{end}x{step}", - "Comment": None, - "Priority": 50, - }, - "PluginInfo": { - "SceneFile": None, # Input - "OutputFilePath": None, # Output directory and filename - "OutputFilePrefix": None, - "Version": cmds.about(version=True), # Mandatory for Deadline - "UsingRenderLayers": True, - "RenderLayer": None, # Render only this layer - "Renderer": None, - "ProjectPath": None, # Resolve relative references - "RenderSetupIncludeLights": None, # Include all lights flag. - }, - "AuxFiles": [] # Mandatory for Deadline, may be empty -} +from openpype_modules.deadline import abstract_submit_deadline +from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo -def _format_tiles( - filename, index, tiles_x, tiles_y, - width, height, prefix): - """Generate tile entries for Deadline tile job. - - Returns two dictionaries - one that can be directly used in Deadline - job, second that can be used for Deadline Assembly job configuration - file. - - This will format tile names: - - Example:: - { - "OutputFilename0Tile0": "_tile_1x1_4x4_Main_beauty.1001.exr", - "OutputFilename0Tile1": "_tile_2x1_4x4_Main_beauty.1001.exr" - } - - And add tile prefixes like: - - Example:: - Image prefix is: - `maya///_` - - Result for tile 0 for 4x4 will be: - `maya///_tile_1x1_4x4__` - - Calculating coordinates is tricky as in Job they are defined as top, - left, bottom, right with zero being in top-left corner. But Assembler - configuration file takes tile coordinates as X, Y, Width and Height and - zero is bottom left corner. - - Args: - filename (str): Filename to process as tiles. - index (int): Index of that file if it is sequence. - tiles_x (int): Number of tiles in X. - tiles_y (int): Number if tikes in Y. - width (int): Width resolution of final image. - height (int): Height resolution of final image. - prefix (str): Image prefix. - - Returns: - (dict, dict): Tuple of two dictionaires - first can be used to - extend JobInfo, second has tiles x, y, width and height - used for assembler configuration. - - """ - tile = 0 - out = {"JobInfo": {}, "PluginInfo": {}} - cfg = OrderedDict() - w_space = width / tiles_x - h_space = height / tiles_y - - cfg["TilesCropped"] = "False" - - for tile_x in range(1, tiles_x + 1): - for tile_y in reversed(range(1, tiles_y + 1)): - tile_prefix = "_tile_{}x{}_{}x{}_".format( - tile_x, tile_y, - tiles_x, - tiles_y - ) - out_tile_index = "OutputFilename{}Tile{}".format( - str(index), tile - ) - new_filename = "{}/{}{}".format( - os.path.dirname(filename), - tile_prefix, - os.path.basename(filename) - ) - out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ - "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) - - out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 - out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 - out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 - out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 - - cfg["Tile{}".format(tile)] = new_filename - cfg["Tile{}Tile".format(tile)] = new_filename - cfg["Tile{}FileName".format(tile)] = new_filename - cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space - - cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) - - cfg["Tile{}Width".format(tile)] = w_space - cfg["Tile{}Height".format(tile)] = h_space - - tile += 1 - return out, cfg +@attr.s +class DeadlinePluginInfo(): + SceneFile = attr.ib(default=None) # Input + OutputFilePath = attr.ib(default=None) # Output directory and filename + OutputFilePrefix = attr.ib(default=None) + Version = attr.ib(default=None) # Mandatory for Deadline + UsingRenderLayers = attr.ib(default=True) + RenderLayer = attr.ib(default=None) # Render only this layer + Renderer = attr.ib(default=None) + ProjectPath = attr.ib(default=None) # Resolve relative references + RenderSetupIncludeLights = attr.ib(default=None) # Include all lights flag -def get_renderer_variables(renderlayer, root): - """Retrieve the extension which has been set in the VRay settings. - - Will return None if the current renderer is not VRay - For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which - start with `rs`. Use the actual node name, do NOT use the `nice name` - - Args: - renderlayer (str): the node name of the renderlayer. - root (str): base path to render - - Returns: - dict - - """ - renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) - - padding = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["padding"])) - - filename_0 = cmds.renderSettings( - fullPath=True, - gin="#" * int(padding), - lut=True, - layer=renderlayer or lib.get_current_renderlayer())[0] - filename_0 = re.sub('_', '_beauty', - filename_0, flags=re.IGNORECASE) - prefix_attr = "defaultRenderGlobals.imageFilePrefix" - - scene = cmds.file(query=True, sceneName=True) - scene, _ = os.path.splitext(os.path.basename(scene)) - - if renderer == "vray": - renderlayer = renderlayer.split("_")[-1] - # Maya's renderSettings function does not return V-Ray file extension - # so we get the extension from vraySettings - extension = cmds.getAttr("vraySettings.imageFormatStr") - - # When V-Ray image format has not been switched once from default .png - # the getAttr command above returns None. As such we explicitly set - # it to `.png` - if extension is None: - extension = "png" - - if extension in ["exr (multichannel)", "exr (deep)"]: - extension = "exr" - - prefix_attr = "vraySettings.fileNamePrefix" - filename_prefix = cmds.getAttr(prefix_attr) - # we need to determine path for vray as maya `renderSettings` query - # does not work for vray. - - filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = "{}.{}.{}".format( - filename_0, "#" * int(padding), extension) - filename_0 = os.path.normpath(os.path.join(root, filename_0)) - elif renderer == "renderman": - prefix_attr = "rmanGlobals.imageFileFormat" - # NOTE: This is guessing extensions from renderman display types. - # Some of them are just framebuffers, d_texture format can be - # set in display setting. We set those now to None, but it - # should be handled more gracefully. - display_types = { - "d_deepexr": "exr", - "d_it": None, - "d_null": None, - "d_openexr": "exr", - "d_png": "png", - "d_pointcloud": "ptc", - "d_targa": "tga", - "d_texture": None, - "d_tiff": "tif" - } - - extension = display_types.get( - cmds.listConnections("rmanDefaultDisplay.displayType")[0], - "exr" - ) or "exr" - - filename_prefix = "{}/{}".format( - cmds.getAttr("rmanGlobals.imageOutputDir"), - cmds.getAttr("rmanGlobals.imageFileFormat") - ) - - renderlayer = renderlayer.split("_")[-1] - - filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', "#" * int(padding), filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', extension, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = os.path.normpath(os.path.join(root, filename_0)) - elif renderer == "redshift": - # mapping redshift extension dropdown values to strings - ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] - extension = ext_mapping[ - cmds.getAttr("redshiftOptions.imageFormat") - ] - else: - # Get the extension, getAttr defaultRenderGlobals.imageFormat - # returns an index number. - filename_base = os.path.basename(filename_0) - extension = os.path.splitext(filename_base)[-1].strip(".") - - filename_prefix = cmds.getAttr(prefix_attr) - return {"ext": extension, - "filename_prefix": filename_prefix, - "padding": padding, - "filename_0": filename_0} - - -class MayaSubmitDeadline(pyblish.api.InstancePlugin): +class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): """Submit available render layers to Deadline. Renders are submitted to a Deadline Web Service as @@ -284,15 +63,12 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): """ - label = "Submit to Deadline" - order = pyblish.api.IntegratorOrder + 0.1 + label = "Submit Render to Deadline" hosts = ["maya"] families = ["renderlayer"] targets = ["local"] - use_published = True tile_assembler_plugin = "OpenPypeTileAssembler" - asset_dependencies = False priority = 50 tile_priority = 50 limit_groups = [] @@ -300,32 +76,173 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): pluginInfo = {} group = "none" - def process(self, instance): - """Plugin entry point.""" - instance.data["toBeRenderedOn"] = "deadline" + def get_job_info(self): + job_info = DeadlineJobInfo(Plugin="MayaBatch") + + # todo: test whether this works for existing production cases + # where custom jobInfo was stored in the project settings + for key, value in self.jobInfo.items(): + setattr(job_info, key, value) + + instance = self._instance context = instance.context - self._instance = instance - self.payload_skeleton = copy.deepcopy(payload_skeleton_template) + filepath = context.data["currentFile"] + filename = os.path.basename(filepath) - # get default deadline webservice url from deadline module - self.deadline_url = instance.context.data.get("defaultDeadline") - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - self.deadline_url = instance.data.get("deadlineUrl") - assert self.deadline_url, "Requires Deadline Webservice URL" + job_info.Name = "%s - %s" % (filename, instance.name) + job_info.BatchName = filename + job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") + job_info.UserName = context.data.get( + "deadlineUser", getpass.getuser()) - # just using existing names from Setting - self._job_info = self.jobInfo + # Deadline requires integers in frame range + frames = "{start}-{end}x{step}".format( + start=int(instance.data["frameStartHandle"]), + end=int(instance.data["frameEndHandle"]), + step=int(instance.data["byFrameStep"]), + ) + job_info.Frames = frames - self._plugin_info = self.pluginInfo + job_info.Pool = instance.data.get("primaryPool") + job_info.SecondaryPool = instance.data.get("secondaryPool") + job_info.ChunkSize = instance.data.get("chunkSize", 10) + job_info.Comment = context.data.get("comment") + job_info.Priority = instance.data.get("priority", self.priority) + + if self.group != "none" and self.group: + job_info.Group = self.group + + if self.limit_groups: + job_info.LimitGroups = ",".join(self.limit_groups) + + self.payload_skeleton["JobInfo"]["Name"] = jobname + self.payload_skeleton["JobInfo"]["BatchName"] = src_filename + + # Optional, enable double-click to preview rendered + # frames from Deadline Monitor + self.payload_skeleton["JobInfo"]["OutputDirectory0"] = \ + os.path.dirname(output_filename_0).replace("\\", "/") + self.payload_skeleton["JobInfo"]["OutputFilename0"] = \ + output_filename_0.replace("\\", "/") + + # Add options from RenderGlobals------------------------------------- + render_globals = instance.data.get("renderGlobals", {}) + self.payload_skeleton["JobInfo"].update(render_globals) + + keys = [ + "FTRACK_API_KEY", + "FTRACK_API_USER", + "FTRACK_SERVER", + "OPENPYPE_SG_USER", + "AVALON_PROJECT", + "AVALON_ASSET", + "AVALON_TASK", + "AVALON_APP_NAME", + "OPENPYPE_DEV", + "OPENPYPE_LOG_NO_COLORS", + "OPENPYPE_VERSION" + ] + # Add mongo url if it's enabled + if self._instance.context.data.get("deadlinePassMongoUrl"): + keys.append("OPENPYPE_MONGO") + + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **legacy_io.Session) + + + # TODO: Taken from old publish class - test whether still needed + environment["OPENPYPE_LOG_NO_COLORS"] = "1" + environment["OPENPYPE_MAYA_VERSION"] = cmds.about(v=True) + # to recognize job from PYPE for turning Event On/Off + environment["OPENPYPE_RENDER_JOB"] = "1" + + for key in keys: + val = environment.get(key) + if val: + job_info.EnvironmentKeyValue = "{key}={value}".format( + key=key, + value=val + ) + # to recognize job from PYPE for turning Event On/Off + job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + + for i, filepath in enumerate(instance.data["files"]): + dirname = os.path.dirname(filepath) + fname = os.path.basename(filepath) + job_info.OutputDirectory = dirname.replace("\\", "/") + job_info.OutputFilename = fname + + # Adding file dependencies. + if self.asset_dependencies: + dependencies = instance.context.data["fileDependencies"] + dependencies.append(context.data["currentFile"]) + for dependency in dependencies: + job_info.AssetDependency = dependency + + # Add list of expected files to job + # --------------------------------- + exp = instance.data.get("expectedFiles") + + def _get_output_filename(files): + col, rem = clique.assemble(files) + if not col and rem: + # we couldn't find any collections but have + # individual files. + assert len(rem) == 1, ( + "Found multiple non related files " + "to render, don't know what to do " + "with them.") + return rem[0] + else: + return col[0].format('{head}{padding}{tail}') + + if isinstance(exp[0], dict): + # we have aovs and we need to iterate over them + for _aov, files in exp[0].items(): + output_file = _get_output_filename(files) + job_info.OutputFilename = output_file + else: + output_file = _get_output_filename(exp) + job_info.OutputFilename = output_file + + return job_info + + def get_plugin_info(self): + + instance = self._instance + context = instance.context + + renderlayer = instance.data['setMembers'] # rs_beauty + + self.payload_skeleton["PluginInfo"]["RenderLayer"] = renderlayer + self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = instance.data.get("renderSetupIncludeLights") # noqa + + # Output driver to render + plugin_info = DeadlinePluginInfo( + SceneFile=context.data["currentFile"], + Version=cmds.about(version=True), + ) + + return attr.asdict(plugin_info) + + def process_submission(self): + # Override to NOT submit by default when calling super process() method + pass + + def process(self, instance): + super(MayaSubmitDeadline, self).process(instance) + + # TODO: Avoid the need for this logic here, needed for submit publish + # Store output dir for unified publisher (filesequence) + output_dir = os.path.dirname(instance.data["files"][0]) + instance.data["outputDir"] = output_dir + instance.data["toBeRenderedOn"] = "deadline" self.limit_groups = self.limit context = instance.context workspace = context.data["workspaceDir"] - anatomy = context.data['anatomy'] - instance.data["toBeRenderedOn"] = "deadline" filepath = None patches = ( @@ -336,80 +253,24 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "scene_patches", {}) ) - # Handle render/export from published scene or not ------------------ - if self.use_published: - patched_files = [] - for i in context: - if "workfile" not in i.data["families"]: - continue - assert i.data["publish"] is True, ( - "Workfile (scene) must be published along") - template_data = i.data.get("anatomyData") - rep = i.data.get("representations")[0].get("name") - template_data["representation"] = rep - template_data["ext"] = rep - template_data["comment"] = None - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled["publish"]["path"] - filepath = os.path.normpath(template_filled) - self.log.info("Using published scene for render {}".format( - filepath)) + # todo: on self.use_published originally use template_data["representation"] using .get("name") instead of .get("ext") + # todo: on self.use_published replace path for publishRenderMetadataFolder + # todo: on self.use_published apply scene patches to workfile instance + # rep = i.data.get("representations")[0].get("name") - if not os.path.exists(filepath): - self.log.error("published scene does not exist!") - raise - # now we need to switch scene in expected files - # because token will now point to published - # scene file and that might differ from current one - new_scene = os.path.splitext( - os.path.basename(filepath))[0] - orig_scene = os.path.splitext( - os.path.basename(context.data["currentFile"]))[0] - exp = instance.data.get("expectedFiles") + # if instance.data.get("publishRenderMetadataFolder"): + # instance.data["publishRenderMetadataFolder"] = \ + # instance.data["publishRenderMetadataFolder"].replace( + # orig_scene, new_scene) + # self.log.info("Scene name was switched {} -> {}".format( + # orig_scene, new_scene + # )) + # # patch workfile is needed + # if filepath not in patched_files: + # patched_file = self._patch_workfile(filepath, patches) + # patched_files.append(patched_file) - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - new_exp = {} - for aov, files in exp[0].items(): - replaced_files = [] - for f in files: - replaced_files.append( - f.replace(orig_scene, new_scene) - ) - new_exp[aov] = replaced_files - instance.data["expectedFiles"] = [new_exp] - else: - new_exp = [] - for f in exp: - new_exp.append( - f.replace(orig_scene, new_scene) - ) - instance.data["expectedFiles"] = [new_exp] - - if instance.data.get("publishRenderMetadataFolder"): - instance.data["publishRenderMetadataFolder"] = \ - instance.data["publishRenderMetadataFolder"].replace( - orig_scene, new_scene) - self.log.info("Scene name was switched {} -> {}".format( - orig_scene, new_scene - )) - # patch workfile is needed - if filepath not in patched_files: - patched_file = self._patch_workfile(filepath, patches) - patched_files.append(patched_file) - - all_instances = [] - for result in context.data["results"]: - if (result["instance"] is not None and - result["instance"] not in all_instances): # noqa: E128 - all_instances.append(result["instance"]) - - # fallback if nothing was set - if not filepath: - self.log.warning("Falling back to workfile") - filepath = context.data["currentFile"] - - self.log.debug(filepath) + filepath = self.scene_path # collect by super().process # Gather needed data ------------------------------------------------ default_render_file = instance.context.data.get('project_settings')\ @@ -417,10 +278,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): .get('RenderSettings')\ .get('default_render_image_folder') filename = os.path.basename(filepath) - comment = context.data.get("comment", "") dirname = os.path.join(workspace, default_render_file) renderlayer = instance.data['setMembers'] # rs_beauty - deadline_user = context.data.get("user", getpass.getuser()) # Always use the original work file name for the Job name even when # rendering is done from the published Work File. The original work @@ -454,116 +313,34 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): pass # Fill in common data to payload ------------------------------------ - payload_data = {} - payload_data["filename"] = filename - payload_data["filepath"] = filepath - payload_data["jobname"] = jobname - payload_data["deadline_user"] = deadline_user - payload_data["comment"] = comment - payload_data["output_filename_0"] = output_filename_0 - payload_data["render_variables"] = render_variables - payload_data["renderlayer"] = renderlayer - payload_data["workspace"] = workspace - payload_data["dirname"] = dirname - - self.log.info("--- Submission data:") - for k, v in payload_data.items(): - self.log.info("- {}: {}".format(k, v)) - self.log.info("-" * 20) - - frame_pattern = self.payload_skeleton["JobInfo"]["Frames"] - self.payload_skeleton["JobInfo"]["Frames"] = frame_pattern.format( - start=int(self._instance.data["frameStartHandle"]), - end=int(self._instance.data["frameEndHandle"]), - step=int(self._instance.data["byFrameStep"])) - - self.payload_skeleton["JobInfo"]["Plugin"] = self._instance.data.get( - "mayaRenderPlugin", "MayaBatch") - - self.payload_skeleton["JobInfo"]["BatchName"] = src_filename - # Job name, as seen in Monitor - self.payload_skeleton["JobInfo"]["Name"] = jobname - # Arbitrary username, for visualisation in Monitor - self.payload_skeleton["JobInfo"]["UserName"] = deadline_user - # Set job priority - self.payload_skeleton["JobInfo"]["Priority"] = \ - self._instance.data.get("priority", self.priority) - - if self.group != "none" and self.group: - self.payload_skeleton["JobInfo"]["Group"] = self.group - - if self.limit_groups: - self.payload_skeleton["JobInfo"]["LimitGroups"] = \ - ",".join(self.limit_groups) - # Optional, enable double-click to preview rendered - # frames from Deadline Monitor - self.payload_skeleton["JobInfo"]["OutputDirectory0"] = \ - os.path.dirname(output_filename_0).replace("\\", "/") - self.payload_skeleton["JobInfo"]["OutputFilename0"] = \ - output_filename_0.replace("\\", "/") - - self.payload_skeleton["JobInfo"]["Comment"] = comment - self.payload_skeleton["PluginInfo"]["RenderLayer"] = renderlayer - - self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = instance.data.get("renderSetupIncludeLights") # noqa - # Adding file dependencies. - dependencies = instance.context.data["fileDependencies"] - dependencies.append(filepath) - if self.asset_dependencies: - for dependency in dependencies: - key = "AssetDependency" + str(dependencies.index(dependency)) - self.payload_skeleton["JobInfo"][key] = dependency - - # Handle environments ----------------------------------------------- - # We need those to pass them to pype for it to set correct context - keys = [ - "FTRACK_API_KEY", - "FTRACK_API_USER", - "FTRACK_SERVER", - "OPENPYPE_SG_USER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", - "OPENPYPE_DEV", - "OPENPYPE_LOG_NO_COLORS", - "OPENPYPE_VERSION" - ] - # Add mongo url if it's enabled - if instance.context.data.get("deadlinePassMongoUrl"): - keys.append("OPENPYPE_MONGO") - - environment = dict({key: os.environ[key] for key in keys - if key in os.environ}, **legacy_io.Session) - environment["OPENPYPE_LOG_NO_COLORS"] = "1" - environment["OPENPYPE_MAYA_VERSION"] = cmds.about(v=True) - # to recognize job from PYPE for turning Event On/Off - environment["OPENPYPE_RENDER_JOB"] = "1" - self.payload_skeleton["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - # Add options from RenderGlobals------------------------------------- - render_globals = instance.data.get("renderGlobals", {}) - self.payload_skeleton["JobInfo"].update(render_globals) + payload_data = { + "filename": filename, + "filepath": filepath, + "jobname": jobname, + "comment": comment, + "output_filename_0": output_filename_0, + "render_variables": render_variables, + "renderlayer": renderlayer, + "workspace": workspace, + "dirname": dirname, + } # Submit preceding export jobs ------------------------------------- export_job = None assert not all(x in instance.data["families"] for x in ['vrayscene', 'assscene']), ( "Vray Scene and Ass Scene options are mutually exclusive") - if "vrayscene" in instance.data["families"]: - export_job = self._submit_export(payload_data, "vray") - if "assscene" in instance.data["families"]: - export_job = self._submit_export(payload_data, "arnold") - - # Prepare main render job ------------------------------------------- if "vrayscene" in instance.data["families"]: + vray_export_payload = self._get_vray_export_payload(payload_data) + export_job = self.submit(vray_export_payload) + payload = self._get_vray_render_payload(payload_data) + elif "assscene" in instance.data["families"]: + ass_export_payload = self._get_arnold_export_payload(payload_data) + export_job = self.submit(ass_export_payload) + payload = self._get_arnold_render_payload(payload_data) else: payload = self._get_maya_payload(payload_data) @@ -572,267 +349,222 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): if export_job: payload["JobInfo"]["JobDependency0"] = export_job - # Add list of expected files to job --------------------------------- - exp = instance.data.get("expectedFiles") - exp_index = 0 - output_filenames = {} - - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - for _aov, files in exp[0].items(): - col, rem = clique.assemble(files) - if not col and rem: - # we couldn't find any collections but have - # individual files. - assert len(rem) == 1, ("Found multiple non related files " - "to render, don't know what to do " - "with them.") - output_file = rem[0] - if not instance.data.get("tileRendering"): - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - else: - output_file = col[0].format('{head}{padding}{tail}') - if not instance.data.get("tileRendering"): - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - - output_filenames['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - exp_index += 1 - else: - col, rem = clique.assemble(exp) - if not col and rem: - # we couldn't find any collections but have - # individual files. - assert len(rem) == 1, ("Found multiple non related files " - "to render, don't know what to do " - "with them.") - - output_file = rem[0] - if not instance.data.get("tileRendering"): - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - else: - output_file = col[0].format('{head}{padding}{tail}') - if not instance.data.get("tileRendering"): - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - - output_filenames['OutputFilename' + str(exp_index)] = output_file - plugin = payload["JobInfo"]["Plugin"] self.log.info("using render plugin : {}".format(plugin)) # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) - self.preflight_check(instance) - # add jobInfo and pluginInfo variables from Settings - payload["JobInfo"].update(self._job_info) - payload["PluginInfo"].update(self._plugin_info) + payload["JobInfo"].update(self.jobInfo) + payload["PluginInfo"].update(self.pluginInfo) - # Prepare tiles data ------------------------------------------------ if instance.data.get("tileRendering"): - # if we have sequence of files, we need to create tile job for - # every frame + # Prepare tiles data + self._tile_render(instance, payload) + else: + # Submit main render job + self.submit(payload) - payload["JobInfo"]["TileJob"] = True - payload["JobInfo"]["TileJobTilesInX"] = instance.data.get("tilesX") - payload["JobInfo"]["TileJobTilesInY"] = instance.data.get("tilesY") - payload["PluginInfo"]["ImageHeight"] = instance.data.get("resolutionHeight") # noqa: E501 - payload["PluginInfo"]["ImageWidth"] = instance.data.get("resolutionWidth") # noqa: E501 - payload["PluginInfo"]["RegionRendering"] = True + def _tile_render(self, instance, payload): - assembly_payload = { - "AuxFiles": [], - "JobInfo": { - "BatchName": payload["JobInfo"]["BatchName"], - "Frames": 1, - "Name": "{} - Tile Assembly Job".format( - payload["JobInfo"]["Name"]), - "OutputDirectory0": - payload["JobInfo"]["OutputDirectory0"].replace( - "\\", "/"), - "Plugin": self.tile_assembler_plugin, - "MachineLimit": 1 - }, - "PluginInfo": { - "CleanupTiles": 1, - "ErrorOnMissing": True - } + # As collected by super process() + job_info = self.job_info + plugin_info = self.pluginInfo + + # if we have sequence of files, we need to create tile job for + # every frame + + job_info.TileJob = True + job_info.TileJobTilesInX = instance.data.get("tilesX") + job_info.TileJobTilesInY = instance.data.get("tilesY") + + plugin_info["ImageHeight"] = instance.data.get("resolutionHeight") + plugin_info["ImageWidth"] = instance.data.get("resolutionWidth") + plugin_info["RegionRendering"] = True + + assembly_payload = { + "AuxFiles": [], + "JobInfo": { + "BatchName": payload["JobInfo"]["BatchName"], + "Frames": 1, + "Name": "{} - Tile Assembly Job".format( + payload["JobInfo"]["Name"]), + "OutputDirectory0": + payload["JobInfo"]["OutputDirectory0"].replace( + "\\", "/"), + "Plugin": self.tile_assembler_plugin, + "MachineLimit": 1 + }, + "PluginInfo": { + "CleanupTiles": 1, + "ErrorOnMissing": True } - assembly_payload["JobInfo"].update(output_filenames) - assembly_payload["JobInfo"]["Priority"] = self._instance.data.get( - "tile_priority", self.tile_priority) - assembly_payload["JobInfo"]["UserName"] = deadline_user + } + assembly_payload["JobInfo"]["Priority"] = self._instance.data.get( + "tile_priority", self.tile_priority) - frame_payloads = [] - assembly_payloads = [] + frame_payloads = [] + assembly_payloads = [] - R_FRAME_NUMBER = re.compile(r".+\.(?P[0-9]+)\..+") # noqa: N806, E501 - REPL_FRAME_NUMBER = re.compile(r"(.+\.)([0-9]+)(\..+)") # noqa: N806, E501 + R_FRAME_NUMBER = re.compile( + r".+\.(?P[0-9]+)\..+") # noqa: N806, E501 + REPL_FRAME_NUMBER = re.compile( + r"(.+\.)([0-9]+)(\..+)") # noqa: N806, E501 - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - # get files from `beauty` - files = exp[0].get("beauty") - # assembly files are used for assembly jobs as we need to put - # together all AOVs - assembly_files = list( - itertools.chain.from_iterable( - [f for _, f in exp[0].items()])) - if not files: - # if beauty doesn't exists, use first aov we found - files = exp[0].get(list(exp[0].keys())[0]) - else: - files = exp - assembly_files = files + exp = instance.data["expectedFiles"] + if isinstance(exp[0], dict): + # we have aovs and we need to iterate over them + # get files from `beauty` + files = exp[0].get("beauty") + # assembly files are used for assembly jobs as we need to put + # together all AOVs + assembly_files = list( + itertools.chain.from_iterable( + [f for _, f in exp[0].items()])) + if not files: + # if beauty doesn't exists, use first aov we found + files = exp[0].get(list(exp[0].keys())[0]) + else: + files = exp + assembly_files = files - frame_jobs = {} + frame_jobs = {} - file_index = 1 - for file in files: - frame = re.search(R_FRAME_NUMBER, file).group("frame") - new_payload = copy.deepcopy(payload) - new_payload["JobInfo"]["Name"] = \ - "{} (Frame {} - {} tiles)".format( - payload["JobInfo"]["Name"], - frame, - instance.data.get("tilesX") * instance.data.get("tilesY") # noqa: E501 + file_index = 1 + for file in files: + frame = re.search(R_FRAME_NUMBER, file).group("frame") + new_payload = copy.deepcopy(payload) + new_payload["JobInfo"]["Name"] = \ + "{} (Frame {} - {} tiles)".format( + payload["JobInfo"]["Name"], + frame, + instance.data.get("tilesX") * instance.data.get("tilesY") + # noqa: E501 ) - self.log.info( - "... preparing job {}".format( - new_payload["JobInfo"]["Name"])) - new_payload["JobInfo"]["TileJobFrame"] = frame + self.log.info( + "... preparing job {}".format( + new_payload["JobInfo"]["Name"])) + new_payload["JobInfo"]["TileJobFrame"] = frame - tiles_data = _format_tiles( + tiles_data = _format_tiles( + file, 0, + instance.data.get("tilesX"), + instance.data.get("tilesY"), + instance.data.get("resolutionWidth"), + instance.data.get("resolutionHeight"), + payload["PluginInfo"]["OutputFilePrefix"] + )[0] + new_payload["JobInfo"].update(tiles_data["JobInfo"]) + new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) + + self.log.info("hashing {} - {}".format(file_index, file)) + job_hash = hashlib.sha256( + ("{}_{}".format(file_index, file)).encode("utf-8")) + frame_jobs[frame] = job_hash.hexdigest() + new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() + new_payload["JobInfo"]["ExtraInfo1"] = file + + frame_payloads.append(new_payload) + file_index += 1 + + file_index = 1 + for file in assembly_files: + frame = re.search(R_FRAME_NUMBER, file).group("frame") + new_assembly_payload = copy.deepcopy(assembly_payload) + new_assembly_payload["JobInfo"]["Name"] = \ + "{} (Frame {})".format( + assembly_payload["JobInfo"]["Name"], + frame) + new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( + REPL_FRAME_NUMBER, + "\\1{}\\3".format("#" * len(frame)), file) + + new_assembly_payload["PluginInfo"]["Renderer"] = \ + self._instance.data["renderer"] # noqa: E501 + new_assembly_payload["JobInfo"]["ExtraInfo0"] = frame_jobs[ + frame] # noqa: E501 + new_assembly_payload["JobInfo"]["ExtraInfo1"] = file + assembly_payloads.append(new_assembly_payload) + file_index += 1 + + self.log.info( + "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) + + url = "{}/api/jobs".format(self.deadline_url) + tiles_count = instance.data.get("tilesX") * instance.data.get( + "tilesY") # noqa: E501 + + for tile_job in frame_payloads: + response = self.submit(tile_job) + + job_id = response.json()["_id"] + hash = response.json()["Props"]["Ex0"] + + for assembly_job in assembly_payloads: + if assembly_job["JobInfo"]["ExtraInfo0"] == hash: + assembly_job["JobInfo"]["JobDependency0"] = job_id + + for assembly_job in assembly_payloads: + file = assembly_job["JobInfo"]["ExtraInfo1"] + # write assembly job config files + now = datetime.now() + + config_file = os.path.join( + os.path.dirname(output_filename_0), + "{}_config_{}.txt".format( + os.path.splitext(file)[0], + now.strftime("%Y_%m_%d_%H_%M_%S") + ) + ) + + try: + if not os.path.isdir(os.path.dirname(config_file)): + os.makedirs(os.path.dirname(config_file)) + except OSError: + # directory is not available + self.log.warning( + "Path is unreachable: `{}`".format( + os.path.dirname(config_file))) + + # add config file as job auxFile + assembly_job["AuxFiles"] = [config_file] + + with open(config_file, "w") as cf: + print("TileCount={}".format(tiles_count), file=cf) + print("ImageFileName={}".format(file), file=cf) + print("ImageWidth={}".format( + instance.data.get("resolutionWidth")), file=cf) + print("ImageHeight={}".format( + instance.data.get("resolutionHeight")), file=cf) + + tiles = _format_tiles( file, 0, instance.data.get("tilesX"), instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), payload["PluginInfo"]["OutputFilePrefix"] - )[0] - new_payload["JobInfo"].update(tiles_data["JobInfo"]) - new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) + )[1] + sorted(tiles) + for k, v in tiles.items(): + print("{}={}".format(k, v), file=cf) - self.log.info("hashing {} - {}".format(file_index, file)) - job_hash = hashlib.sha256( - ("{}_{}".format(file_index, file)).encode("utf-8")) - frame_jobs[frame] = job_hash.hexdigest() - new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() - new_payload["JobInfo"]["ExtraInfo1"] = file + job_idx = 1 + instance.data["assemblySubmissionJobs"] = [] + for ass_job in assembly_payloads: + self.log.info("submitting assembly job {} of {}".format( + job_idx, len(assembly_payloads) + )) + response = self.submit(ass_job) - frame_payloads.append(new_payload) - file_index += 1 + instance.data["assemblySubmissionJobs"].append( + response.json()["_id"]) + job_idx += 1 - file_index = 1 - for file in assembly_files: - frame = re.search(R_FRAME_NUMBER, file).group("frame") - new_assembly_payload = copy.deepcopy(assembly_payload) - new_assembly_payload["JobInfo"]["Name"] = \ - "{} (Frame {})".format( - assembly_payload["JobInfo"]["Name"], - frame) - new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( - REPL_FRAME_NUMBER, - "\\1{}\\3".format("#" * len(frame)), file) - - new_assembly_payload["PluginInfo"]["Renderer"] = self._instance.data["renderer"] # noqa: E501 - new_assembly_payload["JobInfo"]["ExtraInfo0"] = frame_jobs[frame] # noqa: E501 - new_assembly_payload["JobInfo"]["ExtraInfo1"] = file - assembly_payloads.append(new_assembly_payload) - file_index += 1 - - self.log.info( - "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) - - url = "{}/api/jobs".format(self.deadline_url) - tiles_count = instance.data.get("tilesX") * instance.data.get("tilesY") # noqa: E501 - - for tile_job in frame_payloads: - response = requests_post(url, json=tile_job) - if not response.ok: - raise Exception(response.text) - - job_id = response.json()["_id"] - hash = response.json()["Props"]["Ex0"] - - for assembly_job in assembly_payloads: - if assembly_job["JobInfo"]["ExtraInfo0"] == hash: - assembly_job["JobInfo"]["JobDependency0"] = job_id - - for assembly_job in assembly_payloads: - file = assembly_job["JobInfo"]["ExtraInfo1"] - # write assembly job config files - now = datetime.now() - - config_file = os.path.join( - os.path.dirname(output_filename_0), - "{}_config_{}.txt".format( - os.path.splitext(file)[0], - now.strftime("%Y_%m_%d_%H_%M_%S") - ) - ) - - try: - if not os.path.isdir(os.path.dirname(config_file)): - os.makedirs(os.path.dirname(config_file)) - except OSError: - # directory is not available - self.log.warning( - "Path is unreachable: `{}`".format( - os.path.dirname(config_file))) - - # add config file as job auxFile - assembly_job["AuxFiles"] = [config_file] - - with open(config_file, "w") as cf: - print("TileCount={}".format(tiles_count), file=cf) - print("ImageFileName={}".format(file), file=cf) - print("ImageWidth={}".format( - instance.data.get("resolutionWidth")), file=cf) - print("ImageHeight={}".format( - instance.data.get("resolutionHeight")), file=cf) - - tiles = _format_tiles( - file, 0, - instance.data.get("tilesX"), - instance.data.get("tilesY"), - instance.data.get("resolutionWidth"), - instance.data.get("resolutionHeight"), - payload["PluginInfo"]["OutputFilePrefix"] - )[1] - sorted(tiles) - for k, v in tiles.items(): - print("{}={}".format(k, v), file=cf) - - job_idx = 1 - instance.data["assemblySubmissionJobs"] = [] - for ass_job in assembly_payloads: - self.log.info("submitting assembly job {} of {}".format( - job_idx, len(assembly_payloads) - )) - self.log.debug(json.dumps(ass_job, indent=4, sort_keys=True)) - response = requests_post(url, json=ass_job) - if not response.ok: - raise Exception(response.text) - - instance.data["assemblySubmissionJobs"].append( - response.json()["_id"]) - job_idx += 1 - - instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] - self.log.info("Setting batch name on instance: {}".format( - instance.data["jobBatchName"])) - else: - # Submit job to farm -------------------------------------------- - self.log.info("Submitting ...") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(self.deadline_url) - response = requests_post(url, json=payload) - if not response.ok: - raise Exception(response.text) - instance.data["deadlineSubmissionJob"] = response.json() + instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] + self.log.info("Setting batch name on instance: {}".format( + instance.data["jobBatchName"])) def _get_maya_payload(self, data): payload = copy.deepcopy(self.payload_skeleton) @@ -1045,39 +777,6 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload["PluginInfo"].update(plugin_info) return payload - def _submit_export(self, data, format): - if format == "vray": - payload = self._get_vray_export_payload(data) - self.log.info("Submitting vrscene export job.") - elif format == "arnold": - payload = self._get_arnold_export_payload(data) - self.log.info("Submitting ass export job.") - - url = "{}/api/jobs".format(self.deadline_url) - response = requests_post(url, json=payload) - if not response.ok: - self.log.error("Submition failed!") - self.log.error(response.status_code) - self.log.error(response.content) - self.log.debug(payload) - raise RuntimeError(response.text) - - dependency = response.json() - return dependency["_id"] - - def preflight_check(self, instance): - """Ensure the startFrame, endFrame and byFrameStep are integers.""" - for key in ("frameStartHandle", "frameEndHandle", "byFrameStep"): - value = instance.data[key] - - if int(value) == value: - continue - - self.log.warning( - "%f=%d was rounded off to nearest integer" - % (value, int(value)) - ) - def format_vray_output_filename(self, filename, template, dir=False): """Format the expected output file of the Export job. @@ -1160,3 +859,209 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "Applied {} patch to scene.".format( patches[i]["name"])) return file + + +def _format_tiles( + filename, index, tiles_x, tiles_y, + width, height, prefix): + """Generate tile entries for Deadline tile job. + + Returns two dictionaries - one that can be directly used in Deadline + job, second that can be used for Deadline Assembly job configuration + file. + + This will format tile names: + + Example:: + { + "OutputFilename0Tile0": "_tile_1x1_4x4_Main_beauty.1001.exr", + "OutputFilename0Tile1": "_tile_2x1_4x4_Main_beauty.1001.exr" + } + + And add tile prefixes like: + + Example:: + Image prefix is: + `maya///_` + + Result for tile 0 for 4x4 will be: + `maya///_tile_1x1_4x4__` + + Calculating coordinates is tricky as in Job they are defined as top, + left, bottom, right with zero being in top-left corner. But Assembler + configuration file takes tile coordinates as X, Y, Width and Height and + zero is bottom left corner. + + Args: + filename (str): Filename to process as tiles. + index (int): Index of that file if it is sequence. + tiles_x (int): Number of tiles in X. + tiles_y (int): Number if tikes in Y. + width (int): Width resolution of final image. + height (int): Height resolution of final image. + prefix (str): Image prefix. + + Returns: + (dict, dict): Tuple of two dictionaires - first can be used to + extend JobInfo, second has tiles x, y, width and height + used for assembler configuration. + + """ + tile = 0 + out = {"JobInfo": {}, "PluginInfo": {}} + cfg = OrderedDict() + w_space = width / tiles_x + h_space = height / tiles_y + + cfg["TilesCropped"] = "False" + + for tile_x in range(1, tiles_x + 1): + for tile_y in reversed(range(1, tiles_y + 1)): + tile_prefix = "_tile_{}x{}_{}x{}_".format( + tile_x, tile_y, + tiles_x, + tiles_y + ) + out_tile_index = "OutputFilename{}Tile{}".format( + str(index), tile + ) + new_filename = "{}/{}{}".format( + os.path.dirname(filename), + tile_prefix, + os.path.basename(filename) + ) + out["JobInfo"][out_tile_index] = new_filename + out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ + "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) + + out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 + out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 + out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 + out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + + cfg["Tile{}".format(tile)] = new_filename + cfg["Tile{}Tile".format(tile)] = new_filename + cfg["Tile{}FileName".format(tile)] = new_filename + cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space + + cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) + + cfg["Tile{}Width".format(tile)] = w_space + cfg["Tile{}Height".format(tile)] = h_space + + tile += 1 + return out, cfg + + +def get_renderer_variables(renderlayer, root): + """Retrieve the extension which has been set in the VRay settings. + + Will return None if the current renderer is not VRay + For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which + start with `rs`. Use the actual node name, do NOT use the `nice name` + + Args: + renderlayer (str): the node name of the renderlayer. + root (str): base path to render + + Returns: + dict + + """ + renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) + render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) + + padding = cmds.getAttr("{}.{}".format(render_attrs["node"], + render_attrs["padding"])) + + filename_0 = cmds.renderSettings( + fullPath=True, + gin="#" * int(padding), + lut=True, + layer=renderlayer or lib.get_current_renderlayer())[0] + filename_0 = re.sub('_', '_beauty', + filename_0, flags=re.IGNORECASE) + prefix_attr = "defaultRenderGlobals.imageFilePrefix" + + scene = cmds.file(query=True, sceneName=True) + scene, _ = os.path.splitext(os.path.basename(scene)) + + if renderer == "vray": + renderlayer = renderlayer.split("_")[-1] + # Maya's renderSettings function does not return V-Ray file extension + # so we get the extension from vraySettings + extension = cmds.getAttr("vraySettings.imageFormatStr") + + # When V-Ray image format has not been switched once from default .png + # the getAttr command above returns None. As such we explicitly set + # it to `.png` + if extension is None: + extension = "png" + + if extension in ["exr (multichannel)", "exr (deep)"]: + extension = "exr" + + prefix_attr = "vraySettings.fileNamePrefix" + filename_prefix = cmds.getAttr(prefix_attr) + # we need to determine path for vray as maya `renderSettings` query + # does not work for vray. + + filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = "{}.{}.{}".format( + filename_0, "#" * int(padding), extension) + filename_0 = os.path.normpath(os.path.join(root, filename_0)) + elif renderer == "renderman": + prefix_attr = "rmanGlobals.imageFileFormat" + # NOTE: This is guessing extensions from renderman display types. + # Some of them are just framebuffers, d_texture format can be + # set in display setting. We set those now to None, but it + # should be handled more gracefully. + display_types = { + "d_deepexr": "exr", + "d_it": None, + "d_null": None, + "d_openexr": "exr", + "d_png": "png", + "d_pointcloud": "ptc", + "d_targa": "tga", + "d_texture": None, + "d_tiff": "tif" + } + + extension = display_types.get( + cmds.listConnections("rmanDefaultDisplay.displayType")[0], + "exr" + ) or "exr" + + filename_prefix = "{}/{}".format( + cmds.getAttr("rmanGlobals.imageOutputDir"), + cmds.getAttr("rmanGlobals.imageFileFormat") + ) + + renderlayer = renderlayer.split("_")[-1] + + filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', "#" * int(padding), filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', extension, filename_0, flags=re.IGNORECASE) # noqa: E501 + filename_0 = os.path.normpath(os.path.join(root, filename_0)) + elif renderer == "redshift": + # mapping redshift extension dropdown values to strings + ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] + extension = ext_mapping[ + cmds.getAttr("redshiftOptions.imageFormat") + ] + else: + # Get the extension, getAttr defaultRenderGlobals.imageFormat + # returns an index number. + filename_base = os.path.basename(filename_0) + extension = os.path.splitext(filename_base)[-1].strip(".") + + filename_prefix = cmds.getAttr(prefix_attr) + return {"ext": extension, + "filename_prefix": filename_prefix, + "padding": padding, + "filename_0": filename_0} + + From aaa95efb3c80080166de20c9f48bbfe6a8c11548 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Aug 2022 13:45:53 +0200 Subject: [PATCH 091/513] Removed submodule vendor/configs/OpenColorIO-Configs --- vendor/configs/OpenColorIO-Configs | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs deleted file mode 160000 index 0bb079c08b..0000000000 --- a/vendor/configs/OpenColorIO-Configs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953 From 4c16b8930fd38d6c9f792c5983d6e2e88f1587ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 24 Aug 2022 14:14:47 +0200 Subject: [PATCH 092/513] Update openpype/plugins/publish/collect_audio.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/collect_audio.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index e2fb766ec4..0825c281ad 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -66,7 +66,10 @@ class CollectAudio(pyblish.api.InstancePlugin): self.log.info("Audio Data added to instance ...") def _get_repre_doc(self, instance): - cache = instance.context.data.get("__cache_asset_audio", {}) + cache = instance.context.data.get("__cache_asset_audio") + if cache is None: + cache = {} + instance.context.data["__cache_asset_audio"] = cache asset_name = instance.data["asset"] # first try to get it from cache From 6ff16a6d25f2b81f5634d526059734225c556ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 24 Aug 2022 14:14:58 +0200 Subject: [PATCH 093/513] Update openpype/plugins/publish/collect_audio.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/collect_audio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/collect_audio.py b/openpype/plugins/publish/collect_audio.py index 0825c281ad..7d53b24e54 100644 --- a/openpype/plugins/publish/collect_audio.py +++ b/openpype/plugins/publish/collect_audio.py @@ -101,6 +101,5 @@ class CollectAudio(pyblish.api.InstancePlugin): # update cache cache[asset_name] = repre_doc - instance.context.data["__cache_asset_audio"].update(cache) return repre_doc From da5353aa2dd1d9e774a72dbc57a6ac5d7368afdd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Aug 2022 14:20:35 +0200 Subject: [PATCH 094/513] git: update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ea5b20eb69..4b773e97ed 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,6 @@ website/.docusaurus mypy.ini tools/run_eventserver.* + +# Developer tools +tools/dev_* From 0f95f87d773ddcbe979fe28d5f0196f1befad38e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 24 Aug 2022 14:59:14 +0200 Subject: [PATCH 095/513] More draft refactoring - still not functional (WIP commit for my own sanity) --- .../plugins/publish/submit_maya_deadline.py | 185 ++++-------------- 1 file changed, 35 insertions(+), 150 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 6dfa48a9f8..5a7d0b98c6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -31,7 +31,6 @@ import clique from maya import cmds -from openpype.hosts.maya.api import lib from openpype.pipeline import legacy_io from openpype_modules.deadline import abstract_submit_deadline @@ -87,11 +86,15 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - filepath = context.data["currentFile"] - filename = os.path.basename(filepath) + # Always use the original work file name for the Job name even when + # rendering is done from the published Work File. The original work + # file name is clearer because it can also have subversion strings, + # etc. which are stripped for the published file. + src_filepath = context.data["currentFile"] + src_filename = os.path.basename(src_filepath) - job_info.Name = "%s - %s" % (filename, instance.name) - job_info.BatchName = filename + job_info.Name = "%s - %s" % (src_filename, instance.name) + job_info.BatchName = src_filename job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") job_info.UserName = context.data.get( "deadlineUser", getpass.getuser()) @@ -116,9 +119,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if self.limit_groups: job_info.LimitGroups = ",".join(self.limit_groups) - self.payload_skeleton["JobInfo"]["Name"] = jobname - self.payload_skeleton["JobInfo"]["BatchName"] = src_filename - # Optional, enable double-click to preview rendered # frames from Deadline Monitor self.payload_skeleton["JobInfo"]["OutputDirectory0"] = \ @@ -227,11 +227,17 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): return attr.asdict(plugin_info) def process_submission(self): - # Override to NOT submit by default when calling super process() method - pass - def process(self, instance): - super(MayaSubmitDeadline, self).process(instance) + instance = self._instance + context = instance.context + + # Generated by AbstractSubmitDeadline. The `job_info`, `plugin_info` + # and `aux_files` are the skeleton payloads that are the basis for + # all the maya submissions + job_info = self.job_info + plugin_info = self.plugin_info + aux_files = self.aux_files + filepath = self.scene_path # publish if `use_publish` else workfile # TODO: Avoid the need for this logic here, needed for submit publish # Store output dir for unified publisher (filesequence) @@ -241,21 +247,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.limit_groups = self.limit - context = instance.context - workspace = context.data["workspaceDir"] - - filepath = None - patches = ( - context.data["project_settings"].get( - "deadline", {}).get( - "publish", {}).get( - "MayaSubmitDeadline", {}).get( - "scene_patches", {}) - ) + # Patch workfile (only when use_published is enabled) + if self.use_published: + patches = ( + context.data["project_settings"].get( + "deadline", {}).get( + "publish", {}).get( + "MayaSubmitDeadline", {}).get( + "scene_patches", {}) + ) + self._patch_workfile(filepath, patches) # todo: on self.use_published originally use template_data["representation"] using .get("name") instead of .get("ext") # todo: on self.use_published replace path for publishRenderMetadataFolder - # todo: on self.use_published apply scene patches to workfile instance # rep = i.data.get("representations")[0].get("name") # if instance.data.get("publishRenderMetadataFolder"): @@ -270,9 +274,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # patched_file = self._patch_workfile(filepath, patches) # patched_files.append(patched_file) - filepath = self.scene_path # collect by super().process - # Gather needed data ------------------------------------------------ + workspace = context.data["workspaceDir"] default_render_file = instance.context.data.get('project_settings')\ .get('maya')\ .get('RenderSettings')\ @@ -281,14 +284,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): dirname = os.path.join(workspace, default_render_file) renderlayer = instance.data['setMembers'] # rs_beauty - # Always use the original work file name for the Job name even when - # rendering is done from the published Work File. The original work - # file name is clearer because it can also have subversion strings, - # etc. which are stripped for the published file. - src_filename = os.path.basename(context.data["currentFile"]) - jobname = "%s - %s" % (src_filename, instance.name) - # Get the variables depending on the renderer + # TODO: Find replacement logic for `get_renderer_variables` through + # what is collected for the render or is implemented in maya + # api `lib_renderproducts` render_variables = get_renderer_variables(renderlayer, dirname) filename_0 = render_variables["filename_0"] if self.use_published: @@ -842,8 +841,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): str: Patched file path or None """ - if os.path.splitext(file)[1].lower() != ".ma" or not patches: - return None + if not patches or os.path.splitext(file)[1].lower() != ".ma": + return compiled_regex = [re.compile(p["regex"]) for p in patches] with open(file, "r+") as pf: @@ -931,7 +930,7 @@ def _format_tiles( os.path.basename(filename) ) out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ + out["PluginInfo"]["RegionPrefix{}".format(tile)] = \ "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 @@ -951,117 +950,3 @@ def _format_tiles( tile += 1 return out, cfg - - -def get_renderer_variables(renderlayer, root): - """Retrieve the extension which has been set in the VRay settings. - - Will return None if the current renderer is not VRay - For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which - start with `rs`. Use the actual node name, do NOT use the `nice name` - - Args: - renderlayer (str): the node name of the renderlayer. - root (str): base path to render - - Returns: - dict - - """ - renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) - - padding = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["padding"])) - - filename_0 = cmds.renderSettings( - fullPath=True, - gin="#" * int(padding), - lut=True, - layer=renderlayer or lib.get_current_renderlayer())[0] - filename_0 = re.sub('_', '_beauty', - filename_0, flags=re.IGNORECASE) - prefix_attr = "defaultRenderGlobals.imageFilePrefix" - - scene = cmds.file(query=True, sceneName=True) - scene, _ = os.path.splitext(os.path.basename(scene)) - - if renderer == "vray": - renderlayer = renderlayer.split("_")[-1] - # Maya's renderSettings function does not return V-Ray file extension - # so we get the extension from vraySettings - extension = cmds.getAttr("vraySettings.imageFormatStr") - - # When V-Ray image format has not been switched once from default .png - # the getAttr command above returns None. As such we explicitly set - # it to `.png` - if extension is None: - extension = "png" - - if extension in ["exr (multichannel)", "exr (deep)"]: - extension = "exr" - - prefix_attr = "vraySettings.fileNamePrefix" - filename_prefix = cmds.getAttr(prefix_attr) - # we need to determine path for vray as maya `renderSettings` query - # does not work for vray. - - filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = "{}.{}.{}".format( - filename_0, "#" * int(padding), extension) - filename_0 = os.path.normpath(os.path.join(root, filename_0)) - elif renderer == "renderman": - prefix_attr = "rmanGlobals.imageFileFormat" - # NOTE: This is guessing extensions from renderman display types. - # Some of them are just framebuffers, d_texture format can be - # set in display setting. We set those now to None, but it - # should be handled more gracefully. - display_types = { - "d_deepexr": "exr", - "d_it": None, - "d_null": None, - "d_openexr": "exr", - "d_png": "png", - "d_pointcloud": "ptc", - "d_targa": "tga", - "d_texture": None, - "d_tiff": "tif" - } - - extension = display_types.get( - cmds.listConnections("rmanDefaultDisplay.displayType")[0], - "exr" - ) or "exr" - - filename_prefix = "{}/{}".format( - cmds.getAttr("rmanGlobals.imageOutputDir"), - cmds.getAttr("rmanGlobals.imageFileFormat") - ) - - renderlayer = renderlayer.split("_")[-1] - - filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', "#" * int(padding), filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = re.sub('', extension, filename_0, flags=re.IGNORECASE) # noqa: E501 - filename_0 = os.path.normpath(os.path.join(root, filename_0)) - elif renderer == "redshift": - # mapping redshift extension dropdown values to strings - ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] - extension = ext_mapping[ - cmds.getAttr("redshiftOptions.imageFormat") - ] - else: - # Get the extension, getAttr defaultRenderGlobals.imageFormat - # returns an index number. - filename_base = os.path.basename(filename_0) - extension = os.path.splitext(filename_base)[-1].strip(".") - - filename_prefix = cmds.getAttr(prefix_attr) - return {"ext": extension, - "filename_prefix": filename_prefix, - "padding": padding, - "filename_0": filename_0} - - From 1615f74af3e8daddb8c625329f67f0756deb9bc9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 24 Aug 2022 15:40:02 +0200 Subject: [PATCH 096/513] OP-3214 - updated format of addon info response When downloading it should go through each source until it succeeds --- distribution/addon_distribution.py | 37 +++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index 3cc2374b93..2efbb34274 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -4,6 +4,7 @@ from abc import abstractmethod import attr import logging import requests +import platform from distribution.file_handler import RemoteFileHandler @@ -14,13 +15,26 @@ class UrlType(Enum): FILESYSTEM = "filesystem" +@attr.s +class MultiPlatformPath(object): + windows = attr.ib(default=None) + linux = attr.ib(default=None) + darwin = attr.ib(default=None) + + +@attr.s +class AddonSource(object): + type = attr.ib() + url = attr.ib(default=None) + path = attr.ib(default=attr.Factory(MultiPlatformPath)) + + @attr.s class AddonInfo(object): """Object matching json payload from Server""" - name = attr.ib(default=None) - version = attr.ib(default=None) - addon_url = attr.ib(default=None) - type = attr.ib(default=None) + name = attr.ib() + version = attr.ib() + sources = attr.ib(default=attr.Factory(list), type=AddonSource) hash = attr.ib(default=None) description = attr.ib(default=None) license = attr.ib(default=None) @@ -44,12 +58,11 @@ class AddonDownloader: @classmethod @abstractmethod - def download(cls, addon_url, destination): + def download(cls, source, destination): """Returns url to downloaded addon zip file. Args: - addon_url (str): http or OS or any supported protocol url to addon - zip file + source (dict): {type:"http", "url":"https://} ...} destination (str): local folder to unzip Retursn: (str) local path to addon zip file @@ -90,8 +103,9 @@ class AddonDownloader: class OSAddonDownloader(AddonDownloader): @classmethod - def download(cls, addon_url, destination): + def download(cls, source, destination): # OS doesnt need to download, unzip directly + addon_url = source["path"].get(platform.system().lower()) if not os.path.exists(addon_url): raise ValueError("{} is not accessible".format(addon_url)) return addon_url @@ -101,14 +115,15 @@ class HTTPAddonDownloader(AddonDownloader): CHUNK_SIZE = 100000 @classmethod - def download(cls, addon_url, destination): - cls.log.debug(f"Downloading {addon_url} to {destination}") + def download(cls, source, destination): + source_url = source["url"] + cls.log.debug(f"Downloading {source_url} to {destination}") file_name = os.path.basename(destination) _, ext = os.path.splitext(file_name) if (ext.replace(".", '') not in set(RemoteFileHandler.IMPLEMENTED_ZIP_FORMATS)): file_name += ".zip" - RemoteFileHandler.download_url(addon_url, + RemoteFileHandler.download_url(source_url, destination, filename=file_name) From 34c15c24292a3ae434b509987acb9b28f8176106 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 24 Aug 2022 15:41:03 +0200 Subject: [PATCH 097/513] OP-3214 - fixed update_addon_state Should be able to update whatever can. --- distribution/addon_distribution.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index 2efbb34274..f5af0f77ed 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -170,26 +170,36 @@ def update_addon_state(addon_infos, destination_folder, factory, factory (AddonDownloader): factory to get appropriate downloader per addon type log (logging.Logger) + Returns: + (dict): {"addon_full_name":"exists"|"updated"|"failed" """ if not log: log = logging.getLogger(__name__) + download_states = {} for addon in addon_infos: full_name = "{}_{}".format(addon.name, addon.version) addon_dest = os.path.join(destination_folder, full_name) if os.path.isdir(addon_dest): log.debug(f"Addon version folder {addon_dest} already exists.") + download_states[full_name] = "exists" continue - try: - downloader = factory.get_downloader(addon.type) - zip_file_path = downloader.download(addon.addon_url, addon_dest) - downloader.check_hash(zip_file_path, addon.hash) - downloader.unzip(zip_file_path, addon_dest) - except Exception: - log.warning(f"Error happened during updating {addon.name}", - exc_info=True) + for source in addon.sources: + download_states[full_name] = "failed" + try: + downloader = factory.get_downloader(source["type"]) + zip_file_path = downloader.download(source, addon_dest) + downloader.check_hash(zip_file_path, addon.hash) + downloader.unzip(zip_file_path, addon_dest) + download_states[full_name] = "updated" + break + except Exception: + log.warning(f"Error happened during updating {addon.name}", + exc_info=True) + + return download_states def check_addons(server_endpoint, addon_folder, downloaders): From 437ead97762c861172f19901d0d83d8ea11b7b2b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 24 Aug 2022 15:54:06 +0200 Subject: [PATCH 098/513] OP-3214 - introduced update state enum --- distribution/addon_distribution.py | 33 ++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index f5af0f77ed..4ca3f5687a 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -15,6 +15,11 @@ class UrlType(Enum): FILESYSTEM = "filesystem" +class UpdateState(Enum): + EXISTS = "exists" + UPDATED = "updated" + FAILED = "failed" + @attr.s class MultiPlatformPath(object): windows = attr.ib(default=None) @@ -171,7 +176,8 @@ def update_addon_state(addon_infos, destination_folder, factory, addon type log (logging.Logger) Returns: - (dict): {"addon_full_name":"exists"|"updated"|"failed" + (dict): {"addon_full_name": UpdateState.value + (eg. "exists"|"updated"|"failed") """ if not log: log = logging.getLogger(__name__) @@ -183,17 +189,17 @@ def update_addon_state(addon_infos, destination_folder, factory, if os.path.isdir(addon_dest): log.debug(f"Addon version folder {addon_dest} already exists.") - download_states[full_name] = "exists" + download_states[full_name] = UpdateState.EXISTS.value continue for source in addon.sources: - download_states[full_name] = "failed" + download_states[full_name] = UpdateState.FAILED.value try: downloader = factory.get_downloader(source["type"]) zip_file_path = downloader.download(source, addon_dest) downloader.check_hash(zip_file_path, addon.hash) downloader.unzip(zip_file_path, addon_dest) - download_states[full_name] = "updated" + download_states[full_name] = UpdateState.UPDATED.value break except Exception: log.warning(f"Error happened during updating {addon.name}", @@ -203,11 +209,22 @@ def update_addon_state(addon_infos, destination_folder, factory, def check_addons(server_endpoint, addon_folder, downloaders): - """Main entry point to compare existing addons with those on server.""" + """Main entry point to compare existing addons with those on server. + + Args: + server_endpoint (str): url to v4 server endpoint + addon_folder (str): local dir path for addons + downloaders (AddonDownloader): factory of downloaders + + Raises: + (RuntimeError) if any addon failed update + """ addons_info = get_addons_info(server_endpoint) - update_addon_state(addons_info, - addon_folder, - downloaders) + result = update_addon_state(addons_info, + addon_folder, + downloaders) + if UpdateState.FAILED.value in result.values(): + raise RuntimeError(f"Unable to update some addons {result}") def cli(args): From 0d495a36834d0e5aec062841c3f3820f68900c0a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 24 Aug 2022 16:01:37 +0200 Subject: [PATCH 099/513] OP-3214 - added unit tests --- distribution/__init__.py | 0 .../tests/test_addon_distributtion.py | 121 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 distribution/__init__.py create mode 100644 distribution/tests/test_addon_distributtion.py diff --git a/distribution/__init__.py b/distribution/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/distribution/tests/test_addon_distributtion.py b/distribution/tests/test_addon_distributtion.py new file mode 100644 index 0000000000..2e81bc4ef9 --- /dev/null +++ b/distribution/tests/test_addon_distributtion.py @@ -0,0 +1,121 @@ +import pytest +import attr +import tempfile + +from distribution.addon_distribution import ( + AddonDownloader, + UrlType, + OSAddonDownloader, + HTTPAddonDownloader, + AddonInfo, + update_addon_state, + UpdateState +) + + +@pytest.fixture +def addon_downloader(): + addon_downloader = AddonDownloader() + addon_downloader.register_format(UrlType.FILESYSTEM, OSAddonDownloader) + addon_downloader.register_format(UrlType.HTTP, HTTPAddonDownloader) + + yield addon_downloader + + +@pytest.fixture +def http_downloader(addon_downloader): + yield addon_downloader.get_downloader(UrlType.HTTP.value) + + +@pytest.fixture +def temp_folder(): + yield tempfile.mkdtemp() + + +@pytest.fixture +def sample_addon_info(): + addon_info = { + "name": "openpype_slack", + "version": "1.0.0", + "sources": [ + { + "type": "http", + "url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing" + }, + { + "type": "filesystem", + "path": { + "windows": ["P:/sources/some_file.zip", "W:/sources/some_file.zip"], + "linux": ["/mnt/srv/sources/some_file.zip"], + "darwin": ["/Volumes/srv/sources/some_file.zip"] + } + } + ], + "hash": "4f6b8568eb9dd6f510fd7c4dcb676788" + } + yield addon_info + + +def test_register(printer): + addon_downloader = AddonDownloader() + + assert len(addon_downloader._downloaders) == 0, "Contains registered" + + addon_downloader.register_format(UrlType.FILESYSTEM, OSAddonDownloader) + assert len(addon_downloader._downloaders) == 1, "Should contain one" + + +def test_get_downloader(printer, addon_downloader): + assert addon_downloader.get_downloader(UrlType.FILESYSTEM.value), "Should find" # noqa + + with pytest.raises(ValueError): + addon_downloader.get_downloader("unknown"), "Shouldn't find" + + +def test_addon_info(printer, sample_addon_info): + valid_minimum = {"name": "openpype_slack", "version": "1.0.0"} + + assert AddonInfo(**valid_minimum), "Missing required fields" + assert AddonInfo(name=valid_minimum["name"], + version=valid_minimum["version"]), \ + "Missing required fields" + + with pytest.raises(TypeError): + # TODO should be probably implemented + assert AddonInfo(valid_minimum), "Wrong argument format" + + addon = AddonInfo(**sample_addon_info) + assert addon, "Should be created" + assert addon.name == "openpype_slack", "Incorrect name" + assert addon.version == "1.0.0", "Incorrect version" + + with pytest.raises(TypeError): + assert addon["name"], "Dict approach not implemented" + + addon_as_dict = attr.asdict(addon) + assert addon_as_dict["name"], "Dict approach should work" + + with pytest.raises(AttributeError): + # TODO should be probably implemented as . not dict + first_source = addon.sources[0] + assert first_source.type == "http", "Not implemented" + + +def test_update_addon_state(printer, sample_addon_info, + temp_folder, addon_downloader): + addon_info = AddonInfo(**sample_addon_info) + orig_hash = addon_info.hash + + addon_info.hash = "brokenhash" + result = update_addon_state([addon_info], temp_folder, addon_downloader) + assert (result["openpype_slack_1.0.0"] == UpdateState.FAILED.value, + "Hashes not matching") + + addon_info.hash = orig_hash + result = update_addon_state([addon_info], temp_folder, addon_downloader) + assert (result["openpype_slack_1.0.0"] == UpdateState.UPDATED.value, + "Failed updating") + + result = update_addon_state([addon_info], temp_folder, addon_downloader) + assert (result["openpype_slack_1.0.0"] == UpdateState.EXISTS.value, + "Tried to update") From be5dbd6512362c58abb5ec9415414903e8badb20 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 24 Aug 2022 16:03:57 +0200 Subject: [PATCH 100/513] OP-3214 - Hound --- distribution/addon_distribution.py | 1 + distribution/tests/test_addon_distributtion.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index 4ca3f5687a..95d0b5e397 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -20,6 +20,7 @@ class UpdateState(Enum): UPDATED = "updated" FAILED = "failed" + @attr.s class MultiPlatformPath(object): windows = attr.ib(default=None) diff --git a/distribution/tests/test_addon_distributtion.py b/distribution/tests/test_addon_distributtion.py index 2e81bc4ef9..e67ca3c479 100644 --- a/distribution/tests/test_addon_distributtion.py +++ b/distribution/tests/test_addon_distributtion.py @@ -40,12 +40,12 @@ def sample_addon_info(): "sources": [ { "type": "http", - "url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing" + "url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing" # noqa }, { "type": "filesystem", "path": { - "windows": ["P:/sources/some_file.zip", "W:/sources/some_file.zip"], + "windows": ["P:/sources/some_file.zip", "W:/sources/some_file.zip"], # noqa "linux": ["/mnt/srv/sources/some_file.zip"], "darwin": ["/Volumes/srv/sources/some_file.zip"] } @@ -108,14 +108,14 @@ def test_update_addon_state(printer, sample_addon_info, addon_info.hash = "brokenhash" result = update_addon_state([addon_info], temp_folder, addon_downloader) - assert (result["openpype_slack_1.0.0"] == UpdateState.FAILED.value, - "Hashes not matching") + assert result["openpype_slack_1.0.0"] == UpdateState.FAILED.value, \ + "Hashes not matching" addon_info.hash = orig_hash result = update_addon_state([addon_info], temp_folder, addon_downloader) - assert (result["openpype_slack_1.0.0"] == UpdateState.UPDATED.value, - "Failed updating") + assert result["openpype_slack_1.0.0"] == UpdateState.UPDATED.value, \ + "Failed updating" result = update_addon_state([addon_info], temp_folder, addon_downloader) - assert (result["openpype_slack_1.0.0"] == UpdateState.EXISTS.value, - "Tried to update") + assert result["openpype_slack_1.0.0"] == UpdateState.EXISTS.value, \ + "Tried to update" From 390dbb6320f97ba1b05e1de895905b57c62ce1e3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 25 Aug 2022 14:51:21 +0200 Subject: [PATCH 101/513] OP-3682 - added readme to highlight it is for v4 --- distribution/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 distribution/README.md diff --git a/distribution/README.md b/distribution/README.md new file mode 100644 index 0000000000..212eb267b8 --- /dev/null +++ b/distribution/README.md @@ -0,0 +1,18 @@ +Addon distribution tool +------------------------ + +Code in this folder is backend portion of Addon distribution logic for v4 server. + +Each host, module will be separate Addon in the future. Each v4 server could run different set of Addons. + +Client (running on artist machine) will in the first step ask v4 for list of enabled addons. +(It expects list of json documents matching to `addon_distribution.py:AddonInfo` object.) +Next it will compare presence of enabled addon version in local folder. In the case of missing version of +an addon, client will use information in the addon to download (from http/shared local disk/git) zip file +and unzip it. + +Required part of addon distribution will be sharing of dependencies (python libraries, utilities) which is not part of this folder. + +Location of this folder might change in the future as it will be required for a clint to add this folder to sys.path reliably. + +This code needs to be independent on Openpype code as much as possible! \ No newline at end of file From 67fd21edae5d7c3670ee704558ff10505c64a783 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:40:32 +0200 Subject: [PATCH 102/513] imlemented fusion addon --- openpype/hosts/fusion/__init__.py | 10 ++++++++++ openpype/hosts/fusion/addon.py | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 openpype/hosts/fusion/addon.py diff --git a/openpype/hosts/fusion/__init__.py b/openpype/hosts/fusion/__init__.py index e69de29bb2..ddae01890b 100644 --- a/openpype/hosts/fusion/__init__.py +++ b/openpype/hosts/fusion/__init__.py @@ -0,0 +1,10 @@ +from .addon import ( + FusionAddon, + FUSION_HOST_DIR, +) + + +__all__ = ( + "FusionAddon", + "FUSION_HOST_DIR", +) diff --git a/openpype/hosts/fusion/addon.py b/openpype/hosts/fusion/addon.py new file mode 100644 index 0000000000..97fb262517 --- /dev/null +++ b/openpype/hosts/fusion/addon.py @@ -0,0 +1,23 @@ +import os +from openpype.modules import OpenPypeModule +from openpype.modules.interfaces import IHostModule + +FUSION_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class FusionAddon(OpenPypeModule, IHostModule): + name = "fusion" + host_name = "fusion" + + def initialize(self, module_settings): + self.enabled = True + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(FUSION_HOST_DIR, "hooks") + ] + + def get_workfile_extensions(self): + return [".comp"] From 3ea7510693ec23623ec7760f3479e10b363a2adf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:40:40 +0200 Subject: [PATCH 103/513] removed usage of HOST_WORKFILE_EXTENSIONS --- openpype/hosts/fusion/api/workio.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/api/workio.py b/openpype/hosts/fusion/api/workio.py index a1710c6e3a..89752d3e6d 100644 --- a/openpype/hosts/fusion/api/workio.py +++ b/openpype/hosts/fusion/api/workio.py @@ -2,13 +2,11 @@ import sys import os -from openpype.pipeline import HOST_WORKFILE_EXTENSIONS - from .pipeline import get_current_comp def file_extensions(): - return HOST_WORKFILE_EXTENSIONS["fusion"] + return [".comp"] def has_unsaved_changes(): From cf50d1dd1f00fe178d8bfc28a039579222cab1d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Aug 2022 15:40:56 +0200 Subject: [PATCH 104/513] reuse 'FUSION_HOST_DIR' from fusion public api --- openpype/hosts/fusion/api/pipeline.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 54a6c94b60..987eae214b 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -18,12 +18,11 @@ from openpype.pipeline import ( deregister_inventory_action_path, AVALON_CONTAINER_ID, ) -import openpype.hosts.fusion +from openpype.hosts.fusion import FUSION_HOST_DIR log = Logger.get_logger(__name__) -HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.fusion.__file__)) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PLUGINS_DIR = os.path.join(FUSION_HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") From 531682f316bc0ec3212ac31edc2881a988385056 Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 25 Aug 2022 17:17:56 +0200 Subject: [PATCH 105/513] Kitsu|Change: Drop 'entities root' setting. Closes #3738 --- .../modules/kitsu/utils/update_op_with_zou.py | 109 +++++------------- .../defaults/project_settings/kitsu.json | 4 - .../projects_schema/schema_project_kitsu.json | 17 --- 3 files changed, 28 insertions(+), 102 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index e03cf2b30e..7c97e126e5 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -166,50 +166,21 @@ def update_op_assets( # Substitute item type for general classification (assets or shots) if item_type in ["Asset", "AssetType"]: - substitute_item_type = "assets" + entity_root_asset_name = "Assets" elif item_type in ["Episode", "Sequence"]: - substitute_item_type = "shots" - else: - substitute_item_type = f"{item_type.lower()}s" - entity_parent_folders = [ - f - for f in project_module_settings["entities_root"] - .get(substitute_item_type) - .split("/") - if f - ] + entity_root_asset_name = "Shots" # Root parent folder if exist visual_parent_doc_id = ( asset_doc_ids[parent_zou_id]["_id"] if parent_zou_id else None ) if visual_parent_doc_id is None: - # Find root folder docs - root_folder_docs = get_assets( + # Find root folder doc ("Assets" or "Shots") + root_folder_doc = get_asset_by_name( project_name, - asset_names=[entity_parent_folders[-1]], + asset_name=entity_root_asset_name, fields=["_id", "data.root_of"], ) - # NOTE: Not sure why it's checking for entity type? - # OP3 does not support multiple assets with same names so type - # filtering is irelevant. - # This way mimics previous implementation: - # ``` - # root_folder_doc = dbcon.find_one( - # { - # "type": "asset", - # "name": entity_parent_folders[-1], - # "data.root_of": substitute_item_type, - # }, - # ["_id"], - # ) - # ``` - root_folder_doc = None - for folder_doc in root_folder_docs: - root_of = folder_doc.get("data", {}).get("root_of") - if root_of == substitute_item_type: - root_folder_doc = folder_doc - break if root_folder_doc: visual_parent_doc_id = root_folder_doc["_id"] @@ -240,7 +211,7 @@ def update_op_assets( item_name = item["name"] # Set root folders parents - item_data["parents"] = entity_parent_folders + item_data["parents"] + item_data["parents"] = [entity_root_asset_name] + item_data["parents"] # Update 'data' different in zou DB updated_data = { @@ -396,54 +367,30 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): zou_ids_and_asset_docs[project["id"]] = project_doc # Create entities root folders - project_module_settings = get_project_settings(project_name)["kitsu"] - for entity_type, root in project_module_settings["entities_root"].items(): - parent_folders = root.split("/") - direct_parent_doc = None - for i, folder in enumerate(parent_folders, 1): - parent_doc = get_asset_by_name( - project_name, folder, fields=["_id", "data.root_of"] - ) - # NOTE: Not sure why it's checking for entity type? - # OP3 does not support multiple assets with same names so type - # filtering is irelevant. - # Also all of the entities could find be queried at once using - # 'get_assets'. - # This way mimics previous implementation: - # ``` - # parent_doc = dbcon.find_one( - # {"type": "asset", "name": folder, "data.root_of": entity_type} - # ) - # ``` - if ( - parent_doc - and parent_doc.get("data", {}).get("root_of") != entity_type - ): - parent_doc = None - - if not parent_doc: - direct_parent_doc = dbcon.insert_one( - { - "name": folder, - "type": "asset", - "schema": "openpype:asset-3.0", - "data": { - "root_of": entity_type, - "parents": parent_folders[:i], - "visualParent": direct_parent_doc.inserted_id - if direct_parent_doc - else None, - "tasks": {}, - }, - } - ) + to_insert = [ + { + "name": r, + "type": "asset", + "schema": "openpype:asset-3.0", + "data": { + "root_of": r, + "tasks": {}, + }, + } + for r in ["Assets", "Shots"] + if not get_asset_by_name( + project_name, r, fields=["_id", "data.root_of"] + ) + ] # Create - to_insert = [ - create_op_asset(item) - for item in all_entities - if item["id"] not in zou_ids_and_asset_docs.keys() - ] + to_insert.extend( + [ + create_op_asset(item) + for item in all_entities + if item["id"] not in zou_ids_and_asset_docs.keys() + ] + ) if to_insert: # Insert doc in DB dbcon.insert_many(to_insert) diff --git a/openpype/settings/defaults/project_settings/kitsu.json b/openpype/settings/defaults/project_settings/kitsu.json index ba02d8d259..3a9723b9c0 100644 --- a/openpype/settings/defaults/project_settings/kitsu.json +++ b/openpype/settings/defaults/project_settings/kitsu.json @@ -1,8 +1,4 @@ { - "entities_root": { - "assets": "Assets", - "shots": "Shots" - }, "entities_naming_pattern": { "episode": "E##", "sequence": "SQ##", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json index 014a1b7886..fb47670e74 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json @@ -5,23 +5,6 @@ "collapsible": true, "is_file": true, "children": [ - { - "type": "dict", - "key": "entities_root", - "label": "Entities root folder", - "children": [ - { - "type": "text", - "key": "assets", - "label": "Assets:" - }, - { - "type": "text", - "key": "shots", - "label": "Shots (includes Episodes & Sequences if any):" - } - ] - }, { "type": "dict", "key": "entities_naming_pattern", From 38932ba3012668e8ca2a239ffa2b68b002d979d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 27 Aug 2022 10:38:01 +0200 Subject: [PATCH 106/513] fixed interface name --- openpype/hosts/fusion/addon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/addon.py b/openpype/hosts/fusion/addon.py index 97fb262517..e257005061 100644 --- a/openpype/hosts/fusion/addon.py +++ b/openpype/hosts/fusion/addon.py @@ -1,11 +1,11 @@ import os from openpype.modules import OpenPypeModule -from openpype.modules.interfaces import IHostModule +from openpype.modules.interfaces import IHostAddon FUSION_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) -class FusionAddon(OpenPypeModule, IHostModule): +class FusionAddon(OpenPypeModule, IHostAddon): name = "fusion" host_name = "fusion" From 0d8cf12618cee76a5e144429a3459074b14e4adf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 17:14:22 +0200 Subject: [PATCH 107/513] define new source where publish templates are not defined in integrate plubin --- .../defaults/project_settings/global.json | 3 + .../defaults/project_settings/maya.json | 2 +- .../schemas/schema_global_publish.json | 4 ++ .../schemas/schema_global_tools.json | 57 +++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0ff9363ba7..3e00cd725e 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -414,6 +414,9 @@ "filter_families": [] } ] + }, + "publish": { + "template_name_profiles": [] } }, "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets\": {\"characters\": {}, \"locations\": {}}, \"shots\": {}}}", diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 28f6d23e4d..38063bc2c1 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -980,4 +980,4 @@ "ValidateNoAnimation": false } } -} +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index e1aa230b49..c24c88d04a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -642,6 +642,10 @@ ] } }, + { + "type": "label", + "label": "NOTE: Publish template profiles settings were moved to Tools/Publish/Template name profiles. Please move values there." + }, { "type": "list", "key": "template_name_profiles", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index f8c9482e5f..7dc44c2842 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -284,6 +284,63 @@ } } ] + }, + { + "type": "dict", + "key": "publish", + "label": "Publish", + "children": [ + { + "type": "label", + "label": "NOTE: For backwards compatibility can be value empty and in that case are used values from IntegrateAssetNew. This will change in future so please move all values here as soon as possible." + }, + { + "type": "list", + "key": "template_name_profiles", + "label": "Template name profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "label", + "label": "" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template_name", + "label": "Template name" + } + ] + } + } + ] } ] } From 2b6c4659237259b6c691dd2b5dc1db927b47fcd3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:03:30 +0200 Subject: [PATCH 108/513] added helper functions to get template name --- openpype/pipeline/publish/__init__.py | 4 ++ openpype/pipeline/publish/contants.py | 1 + openpype/pipeline/publish/lib.py | 97 ++++++++++++++++++++++++++- 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 openpype/pipeline/publish/contants.py diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index aa7fe0bdbf..a2aa61c4d5 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -10,6 +10,8 @@ from .publish_plugins import ( ) from .lib import ( + get_publish_template_name, + DiscoverResult, publish_plugins_discover, load_help_content_from_plugin, @@ -33,6 +35,8 @@ __all__ = ( "OpenPypePyblishPluginMixin", "OptionalPyblishPluginMixin", + "get_publish_template_name", + "DiscoverResult", "publish_plugins_discover", "load_help_content_from_plugin", diff --git a/openpype/pipeline/publish/contants.py b/openpype/pipeline/publish/contants.py new file mode 100644 index 0000000000..958675ecc1 --- /dev/null +++ b/openpype/pipeline/publish/contants.py @@ -0,0 +1 @@ +DEFAULT_PUBLISH_TEMPLATE = "publish" diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 9060a0bf4b..7c3ea22c06 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -8,8 +8,101 @@ import six import pyblish.plugin import pyblish.api -from openpype.lib import Logger -from openpype.settings import get_project_settings, get_system_settings +from openpype.lib import Logger, filter_profiles +from openpype.settings import ( + get_project_settings, + get_system_settings, +) + +from .contants import DEFAULT_PUBLISH_TEMPLATE + + +def get_template_name_profiles(project_name=None, project_settings=None): + """Receive profiles for publish template keys. + + At least one of arguments must be passed. + + Args: + project_name (str): Name of project where to look for templates. + project_settings(Dic[str, Any]): Prepared project settings. + + Returns: + List[Dict[str, Any]]: Publish template profiles. + """ + + if not project_name and not project_settings: + raise ValueError(( + "Both project name and project settings are missing." + " At least one must be entered." + )) + + if not project_settings: + project_settings = get_project_settings(project_name) + + profiles = ( + project_settings + ["global"] + ["tools"] + ["publish"] + ["template_name_profiles"] + ) + if profiles: + return profiles + + # Use legacy approach for cases new settings are not filled yet for the + # project + return ( + project_settings + ["global"] + ["publish"] + ["IntegrateAssetNew"] + ["template_name_profiles"] + ) + + +def get_publish_template_name( + project_name, + host_name, + family, + task_name, + task_type, + project_settings=None, + logger=None +): + """Get template name which should be used for passed context. + + Publish templates are filtered by host name, family, task name and + task type. + + Default template which is used at if profiles are not available or profile + has empty value is defined by 'DEFAULT_PUBLISH_TEMPLATE' constant. + + Args: + project_name (str): Name of project where to look for settings. + host_name (str): Name of host integration. + family (str): Family for which should be found template. + task_name (str): Task name on which is intance working. + task_type (str): Task type on which is intance working. + project_setting (Dict[str, Any]): Prepared project settings. + logger (logging.Logger): Custom logger used for 'filter_profiles' + function. + + Returns: + str: Template name which should be used for integration. + """ + + template = None + filter_criteria = { + "hosts": host_name, + "families": family, + "tasks": task_name, + "task_types": task_type, + } + profiles = get_template_name_profiles(project_name, project_settings) + profile = filter_profiles(profiles, filter_criteria, logger=logger) + if profile: + template = profile["template_name"] + return template or DEFAULT_PUBLISH_TEMPLATE class DiscoverResult: From 96138a0b73ba6a3f9757283853da9cd1aa85c023 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:22:49 +0200 Subject: [PATCH 109/513] use new functions in integrators --- openpype/plugins/publish/integrate.py | 61 ++++++-------------- openpype/plugins/publish/integrate_legacy.py | 21 +++---- 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index f99c718f8a..56d2621015 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -26,7 +26,10 @@ from openpype.lib import source_hash from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction from openpype.pipeline import legacy_io -from openpype.pipeline.publish import KnownPublishError +from openpype.pipeline.publish import ( + KnownPublishError, + get_publish_template_name, +) log = logging.getLogger(__name__) @@ -792,52 +795,26 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def get_template_name(self, instance): """Return anatomy template name to use for integration""" - # Define publish template name from profiles - filter_criteria = self.get_profile_filter_criteria(instance) - template_name_profiles = self._get_template_name_profiles(instance) - profile = filter_profiles( - template_name_profiles, - filter_criteria, - logger=self.log - ) - - if profile: - return profile["template_name"] - return self.default_template_name - - def _get_template_name_profiles(self, instance): - """Receive profiles for publish template keys. - - Reuse template name profiles from legacy integrator. Goal is to move - the profile settings out of plugin settings but until that happens we - want to be able set it at one place and don't break backwards - compatibility (more then once). - """ - - return ( - instance.context.data["project_settings"] - ["global"] - ["publish"] - ["IntegrateAssetNew"] - ["template_name_profiles"] - ) - - def get_profile_filter_criteria(self, instance): - """Return filter criteria for `filter_profiles`""" # Anatomy data is pre-filled by Collectors - anatomy_data = instance.data["anatomyData"] + + project_name = legacy_io.active_project() # Task can be optional in anatomy data - task = anatomy_data.get("task", {}) + host_name = instance.context.data["hostName"] + anatomy_data = instance.data["anatomyData"] + family = anatomy_data["family"] + task_info = anatomy_data.get("task") or {} - # Return filter criteria - return { - "families": anatomy_data["family"], - "tasks": task.get("name"), - "task_types": task.get("type"), - "hosts": instance.context.data["hostName"], - } + return get_publish_template_name( + project_name, + host_name, + family, + task_name=task_info.get("name"), + task_type=task_info.get("type"), + project_settings=instance.context.data["project_settings"], + logger=self.log + ) def get_rootless_path(self, anatomy, path): """Returns, if possible, path without absolute portion from root diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index b90b61f587..fedaae794a 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -33,6 +33,7 @@ from openpype.lib import ( TemplateUnsolved ) from openpype.pipeline import legacy_io +from openpype.pipeline.publish import get_publish_template_name # this is needed until speedcopy for linux is fixed if sys.platform == "win32": @@ -388,22 +389,16 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): family = self.main_family_from_instance(instance) - key_values = { - "families": family, - "tasks": task_name, - "hosts": instance.context.data["hostName"], - "task_types": task_type - } - profile = filter_profiles( - self.template_name_profiles, - key_values, + template_name = get_publish_template_name( + project_name, + instance.context.data["hostName"], + family, + task_name=task_info.get("name"), + task_type=task_info.get("type"), + project_settings=instance.context.data["project_settings"], logger=self.log ) - template_name = "publish" - if profile: - template_name = profile["template_name"] - published_representations = {} for idx, repre in enumerate(repres): published_files = [] From c7108ac7fbad46fe2aafe669498cb3755d9c7730 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:23:37 +0200 Subject: [PATCH 110/513] modified imports in integrators --- openpype/plugins/publish/integrate.py | 6 +++--- openpype/plugins/publish/integrate_legacy.py | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 56d2621015..8b60ea3b51 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -5,6 +5,9 @@ import copy import clique import six +from bson.objectid import ObjectId +import pyblish.api + from openpype.client.operations import ( OperationsSession, new_subset_document, @@ -14,8 +17,6 @@ from openpype.client.operations import ( prepare_version_update_data, prepare_representation_update_data, ) -from bson.objectid import ObjectId -import pyblish.api from openpype.client import ( get_representations, @@ -23,7 +24,6 @@ from openpype.client import ( get_version_by_name, ) from openpype.lib import source_hash -from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction from openpype.pipeline import legacy_io from openpype.pipeline.publish import ( diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index fedaae794a..0e157c9d1f 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -15,7 +15,6 @@ from bson.objectid import ObjectId from pymongo import DeleteOne, InsertOne import pyblish.api -import openpype.api from openpype.client import ( get_asset_by_name, get_subset_by_id, @@ -25,12 +24,14 @@ from openpype.client import ( get_representations, get_archived_representations, ) -from openpype.lib.profiles_filtering import filter_profiles from openpype.lib import ( prepare_template_data, create_hard_link, StringTemplate, - TemplateUnsolved + TemplateUnsolved, + source_hash, + filter_profiles, + get_local_site_id, ) from openpype.pipeline import legacy_io from openpype.pipeline.publish import get_publish_template_name @@ -1053,7 +1054,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for _src, dest in resources: path = self.get_rootless_path(anatomy, dest) dest = self.get_dest_temp_url(dest) - file_hash = openpype.api.source_hash(dest) + file_hash = source_hash(dest) if self.TMP_FILE_EXT and \ ',{}'.format(self.TMP_FILE_EXT) in file_hash: file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), @@ -1163,7 +1164,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def _get_sites(self, sync_project_presets): """Returns tuple (local_site, remote_site)""" - local_site_id = openpype.api.get_local_site_id() + local_site_id = get_local_site_id() local_site = sync_project_presets["config"]. \ get("active_site", "studio").strip() From c76a1a1dbbe8e705b06ebf02f37237cf7dda98fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:52:43 +0200 Subject: [PATCH 111/513] added settings for hero templates and changed 'tasks' to 'task_names' --- .../defaults/project_settings/global.json | 3 +- .../schemas/schema_global_tools.json | 47 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 3e00cd725e..8692f95a04 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -416,7 +416,8 @@ ] }, "publish": { - "template_name_profiles": [] + "template_name_profiles": [], + "hero_template_name_profiles": [] } }, "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets\": {\"characters\": {}, \"locations\": {}}, \"shots\": {}}}", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index 7dc44c2842..c919cd73c5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -303,9 +303,47 @@ "type": "dict", "children": [ { - "type": "label", - "label": "" + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "template_name", + "label": "Template name" + } + ] + } + }, + { + "type": "list", + "key": "hero_template_name_profiles", + "label": "Hero template name profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ { "key": "families", "label": "Families", @@ -324,7 +362,7 @@ "type": "task-types-enum" }, { - "key": "tasks", + "key": "task_names", "label": "Task names", "type": "list", "object_type": "text" @@ -335,7 +373,8 @@ { "type": "text", "key": "template_name", - "label": "Template name" + "label": "Template name", + "tooltip": "Name of template from Anatomy templates" } ] } From 9d4416719b4a99d50b0d411b5548a8afa8072240 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:53:33 +0200 Subject: [PATCH 112/513] convert legacy to new settings by replacing 'tasks' with 'task_names' --- openpype/pipeline/publish/lib.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 7c3ea22c06..03dfbadfcc 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -2,6 +2,7 @@ import os import sys import types import inspect +import copy import xml.etree.ElementTree import six @@ -47,17 +48,23 @@ def get_template_name_profiles(project_name=None, project_settings=None): ["template_name_profiles"] ) if profiles: - return profiles + return copy.deepcopy(profiles) # Use legacy approach for cases new settings are not filled yet for the # project - return ( + legacy_profiles = ( project_settings ["global"] ["publish"] ["IntegrateAssetNew"] ["template_name_profiles"] ) + # Replace "tasks" key with "task_names" + profiles = [] + for profile in copy.deepcopy(legacy_profiles): + profile["task_names"] = profile.pop("tasks", []) + profiles.append(profile) + return profiles def get_publish_template_name( @@ -95,7 +102,7 @@ def get_publish_template_name( filter_criteria = { "hosts": host_name, "families": family, - "tasks": task_name, + "task_names": task_name, "task_types": task_type, } profiles = get_template_name_profiles(project_name, project_settings) From 63f5b5f2ab40a94c7496b8f08fa19204a5687b5a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:53:49 +0200 Subject: [PATCH 113/513] added ability to get hero version template name --- openpype/pipeline/publish/contants.py | 1 + openpype/pipeline/publish/lib.py | 64 +++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/publish/contants.py b/openpype/pipeline/publish/contants.py index 958675ecc1..169eca2e5c 100644 --- a/openpype/pipeline/publish/contants.py +++ b/openpype/pipeline/publish/contants.py @@ -1 +1,2 @@ DEFAULT_PUBLISH_TEMPLATE = "publish" +DEFAULT_HERO_PUBLISH_TEMPLATE = "hero" diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 03dfbadfcc..85a64da721 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -15,7 +15,10 @@ from openpype.settings import ( get_system_settings, ) -from .contants import DEFAULT_PUBLISH_TEMPLATE +from .contants import ( + DEFAULT_PUBLISH_TEMPLATE, + DEFAULT_HERO_PUBLISH_TEMPLATE, +) def get_template_name_profiles(project_name=None, project_settings=None): @@ -67,6 +70,49 @@ def get_template_name_profiles(project_name=None, project_settings=None): return profiles +def get_hero_template_name_profiles(project_name=None, project_settings=None): + """Receive profiles for hero publish template keys. + + At least one of arguments must be passed. + + Args: + project_name (str): Name of project where to look for templates. + project_settings(Dic[str, Any]): Prepared project settings. + + Returns: + List[Dict[str, Any]]: Publish template profiles. + """ + + if not project_name and not project_settings: + raise ValueError(( + "Both project name and project settings are missing." + " At least one must be entered." + )) + + if not project_settings: + project_settings = get_project_settings(project_name) + + profiles = ( + project_settings + ["global"] + ["tools"] + ["publish"] + ["hero_template_name_profiles"] + ) + if profiles: + return copy.deepcopy(profiles) + + # Use legacy approach for cases new settings are not filled yet for the + # project + return copy.deepcopy( + project_settings + ["global"] + ["publish"] + ["IntegrateHeroVersion"] + ["template_name_profiles"] + ) + + def get_publish_template_name( project_name, host_name, @@ -74,6 +120,7 @@ def get_publish_template_name( task_name, task_type, project_settings=None, + hero=False, logger=None ): """Get template name which should be used for passed context. @@ -105,11 +152,22 @@ def get_publish_template_name( "task_names": task_name, "task_types": task_type, } - profiles = get_template_name_profiles(project_name, project_settings) + if hero: + default_template = DEFAULT_HERO_PUBLISH_TEMPLATE + profiles = get_hero_template_name_profiles( + project_name, project_settings + ) + + else: + profiles = get_template_name_profiles( + project_name, project_settings + ) + default_template = DEFAULT_PUBLISH_TEMPLATE + profile = filter_profiles(profiles, filter_criteria, logger=logger) if profile: template = profile["template_name"] - return template or DEFAULT_PUBLISH_TEMPLATE + return template or default_template class DiscoverResult: From 1698aefcfbc887ba6f29fc59dbdfbc2595d5c6a8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:57:00 +0200 Subject: [PATCH 114/513] use 'get_publish_template_name' in hero integration --- .../plugins/publish/integrate_hero_version.py | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 7d698ff98d..2938c61f8e 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -14,14 +14,12 @@ from openpype.client import ( get_archived_representations, get_representations, ) -from openpype.lib import ( - create_hard_link, - filter_profiles -) +from openpype.lib import create_hard_link from openpype.pipeline import ( schema, legacy_io, ) +from openpype.pipeline.publish import get_publish_template_name class IntegrateHeroVersion(pyblish.api.InstancePlugin): @@ -68,10 +66,11 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): ) return - template_key = self._get_template_key(instance) - anatomy = instance.context.data["anatomy"] project_name = anatomy.project_name + + template_key = self._get_template_key(project_name, instance) + if template_key not in anatomy.templates: self.log.warning(( "!!! Anatomy of project \"{}\" does not have set" @@ -527,30 +526,24 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): return publish_folder - def _get_template_key(self, instance): + def _get_template_key(self, project_name, instance): anatomy_data = instance.data["anatomyData"] - task_data = anatomy_data.get("task") or {} - task_name = task_data.get("name") - task_type = task_data.get("type") + task_info = anatomy_data.get("task") or {} host_name = instance.context.data["hostName"] + # TODO raise error if Hero not set? family = self.main_family_from_instance(instance) - key_values = { - "families": family, - "task_names": task_name, - "task_types": task_type, - "hosts": host_name - } - profile = filter_profiles( - self.template_name_profiles, - key_values, + + return get_publish_template_name( + project_name, + host_name, + family, + task_info.get("name"), + task_info.get("type"), + project_settings=instance.context.data["project_settings"], + hero=True, logger=self.log ) - if profile: - template_name = profile["template_name"] - else: - template_name = self._default_template_name - return template_name def main_family_from_instance(self, instance): """Returns main family of entered instance.""" From 9b7384e1ae96b0f348911e9e163a23857dd2ca7f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 18:57:27 +0200 Subject: [PATCH 115/513] removed unused attribute --- openpype/plugins/publish/integrate_legacy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index 0e157c9d1f..bbf30c9ab7 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -140,7 +140,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): integrated_file_sizes = {} # Attributes set by settings - template_name_profiles = None subset_grouping_profiles = None def process(self, instance): From c6a6e3b21a4aaa6c98450de918c78907fed91f5e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 19:07:34 +0200 Subject: [PATCH 116/513] added warning for access to legacy settings --- openpype/pipeline/publish/lib.py | 35 +++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 85a64da721..29c745ed15 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -21,7 +21,9 @@ from .contants import ( ) -def get_template_name_profiles(project_name=None, project_settings=None): +def get_template_name_profiles( + project_name, project_settings=None, logger=None +): """Receive profiles for publish template keys. At least one of arguments must be passed. @@ -62,6 +64,16 @@ def get_template_name_profiles(project_name=None, project_settings=None): ["IntegrateAssetNew"] ["template_name_profiles"] ) + if legacy_profiles: + if not logger: + logger = Logger.get_logger("get_template_name_profiles") + + logger.warning(( + "Project \"{}\" is using legacy access to publish template." + " It is recommended to move settings to new location" + " 'project_settings/global/tools/publish/template_name_profiles'." + ).format(project_name)) + # Replace "tasks" key with "task_names" profiles = [] for profile in copy.deepcopy(legacy_profiles): @@ -70,7 +82,9 @@ def get_template_name_profiles(project_name=None, project_settings=None): return profiles -def get_hero_template_name_profiles(project_name=None, project_settings=None): +def get_hero_template_name_profiles( + project_name, project_settings=None, logger=None +): """Receive profiles for hero publish template keys. At least one of arguments must be passed. @@ -104,13 +118,24 @@ def get_hero_template_name_profiles(project_name=None, project_settings=None): # Use legacy approach for cases new settings are not filled yet for the # project - return copy.deepcopy( + legacy_profiles = copy.deepcopy( project_settings ["global"] ["publish"] ["IntegrateHeroVersion"] ["template_name_profiles"] ) + if legacy_profiles: + if not logger: + logger = Logger.get_logger("get_hero_template_name_profiles") + + logger.warning(( + "Project \"{}\" is using legacy access to hero publish template." + " It is recommended to move settings to new location" + " 'project_settings/global/tools/publish/" + "hero_template_name_profiles'." + ).format(project_name)) + return legacy_profiles def get_publish_template_name( @@ -155,12 +180,12 @@ def get_publish_template_name( if hero: default_template = DEFAULT_HERO_PUBLISH_TEMPLATE profiles = get_hero_template_name_profiles( - project_name, project_settings + project_name, project_settings, logger ) else: profiles = get_template_name_profiles( - project_name, project_settings + project_name, project_settings, logger ) default_template = DEFAULT_PUBLISH_TEMPLATE From dc4c32b6fc6aaeaedb6bb9d76c7b72d9f5c45c45 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:14:58 +0200 Subject: [PATCH 117/513] Fix representation data for workfile --- openpype/modules/deadline/abstract_submit_deadline.py | 6 +++--- .../deadline/plugins/publish/submit_maya_deadline.py | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 577378335e..d198542370 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -554,9 +554,9 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): "Workfile (scene) must be published along") # determine published path from Anatomy. template_data = i.data.get("anatomyData") - rep = i.data.get("representations")[0].get("ext") - template_data["representation"] = rep - template_data["ext"] = rep + rep = i.data.get("representations")[0] + template_data["representation"] = rep.get("name") + template_data["ext"] = rep.get("ext") template_data["comment"] = None anatomy_filled = anatomy.format(template_data) template_filled = anatomy_filled["publish"]["path"] diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 5a7d0b98c6..68e8eaaa73 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -258,10 +258,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) self._patch_workfile(filepath, patches) - # todo: on self.use_published originally use template_data["representation"] using .get("name") instead of .get("ext") # todo: on self.use_published replace path for publishRenderMetadataFolder - # rep = i.data.get("representations")[0].get("name") - # if instance.data.get("publishRenderMetadataFolder"): # instance.data["publishRenderMetadataFolder"] = \ # instance.data["publishRenderMetadataFolder"].replace( From 67b8664be07fbb8a0061b7c8a62bf4073ef79307 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:15:20 +0200 Subject: [PATCH 118/513] Remove comment for patched file code refactor since it's already implemented --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 68e8eaaa73..07ed237c94 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -266,10 +266,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # self.log.info("Scene name was switched {} -> {}".format( # orig_scene, new_scene # )) - # # patch workfile is needed - # if filepath not in patched_files: - # patched_file = self._patch_workfile(filepath, patches) - # patched_files.append(patched_file) # Gather needed data ------------------------------------------------ workspace = context.data["workspaceDir"] From 2da8f036dee501be62da07d07fa9efafc9e8839f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:19:37 +0200 Subject: [PATCH 119/513] Refactor logic for less indentation --- .../deadline/abstract_submit_deadline.py | 103 +++++++++--------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index d198542370..55e16d8d21 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -546,65 +546,66 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): anatomy = self._instance.context.data['anatomy'] file_path = None for i in self._instance.context: - if "workfile" in i.data["families"] \ - or i.data["family"] == "workfile": - # test if there is instance of workfile waiting - # to be published. - assert i.data["publish"] is True, ( - "Workfile (scene) must be published along") - # determine published path from Anatomy. - template_data = i.data.get("anatomyData") - rep = i.data.get("representations")[0] - template_data["representation"] = rep.get("name") - template_data["ext"] = rep.get("ext") - template_data["comment"] = None - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled["publish"]["path"] - file_path = os.path.normpath(template_filled) - self.log.info("Using published scene for render {}".format( - file_path)) + is_workfile = + if not is_workfile: + continue - if not os.path.exists(file_path): - self.log.error("published scene does not exist!") - raise + # test if there is instance of workfile waiting + # to be published. + assert i.data["publish"] is True, ( + "Workfile (scene) must be published along") + # determine published path from Anatomy. + template_data = i.data.get("anatomyData") + rep = i.data.get("representations")[0] + template_data["representation"] = rep.get("name") + template_data["ext"] = rep.get("ext") + template_data["comment"] = None + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled["publish"]["path"] + file_path = os.path.normpath(template_filled) - if not replace_in_path: - return file_path + self.log.info("Using published scene for render {}".format( + file_path)) - # now we need to switch scene in expected files - # because token will now point to published - # scene file and that might differ from current one - new_scene = os.path.splitext( - os.path.basename(file_path))[0] - orig_scene = os.path.splitext( - os.path.basename( - self._instance.context.data["currentFile"]))[0] - exp = self._instance.data.get("expectedFiles") + if not os.path.exists(file_path): + self.log.error("published scene does not exist!") + raise - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - new_exp = {} - for aov, files in exp[0].items(): - replaced_files = [] - for f in files: - replaced_files.append( - str(f).replace(orig_scene, new_scene) - ) - new_exp[aov] = replaced_files - # [] might be too much here, TODO - self._instance.data["expectedFiles"] = [new_exp] - else: - new_exp = [] - for f in exp: - new_exp.append( + if not replace_in_path: + return file_path + + # now we need to switch scene in expected files + # because token will now point to published + # scene file and that might differ from current one + new_scene = os.path.splitext(os.path.basename(file_path))[0] + orig_scene = os.path.splitext(os.path.basename( + self._instance.context.data["currentFile"]))[0] + exp = self._instance.data.get("expectedFiles") + + if isinstance(exp[0], dict): + # we have aovs and we need to iterate over them + new_exp = {} + for aov, files in exp[0].items(): + replaced_files = [] + for f in files: + replaced_files.append( str(f).replace(orig_scene, new_scene) ) - self._instance.data["expectedFiles"] = new_exp + new_exp[aov] = replaced_files + # [] might be too much here, TODO + self._instance.data["expectedFiles"] = [new_exp] + else: + new_exp = [] + for f in exp: + new_exp.append( + str(f).replace(orig_scene, new_scene) + ) + self._instance.data["expectedFiles"] = new_exp - self.log.info("Scene name was switched {} -> {}".format( - orig_scene, new_scene - )) + self.log.info("Scene name was switched {} -> {}".format( + orig_scene, new_scene + )) return file_path From 21a319b10c46dec6efc2aaf6fe6ac1fe09bfc512 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 29 Aug 2022 19:20:33 +0200 Subject: [PATCH 120/513] added 'deprecated' to integrator labels and added new location for hero templates as note --- .../projects_schema/schemas/schema_global_publish.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index c24c88d04a..2cb0cebf95 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -649,7 +649,7 @@ { "type": "list", "key": "template_name_profiles", - "label": "Template name profiles", + "label": "Template name profiles (DEPRECATED)", "use_label_wrap": true, "object_type": { "type": "dict", @@ -754,10 +754,14 @@ "type": "list", "object_type": "text" }, + { + "type": "label", + "label": "NOTE: Hero publish template profiles settings were moved to Tools/Publish/Hero template name profiles. Please move values there." + }, { "type": "list", "key": "template_name_profiles", - "label": "Template name profiles", + "label": "Template name profiles (DEPRECATED)", "use_label_wrap": true, "object_type": { "type": "dict", From e81e3a7a1021db4e442aa3147ed03ccf8d92d8c6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:22:06 +0200 Subject: [PATCH 121/513] Fix missing line --- openpype/modules/deadline/abstract_submit_deadline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 55e16d8d21..86eebc0d35 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -547,7 +547,10 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): file_path = None for i in self._instance.context: - is_workfile = + is_workfile = ( + "workfile" in i.data.get("families", []) or + i.data["family"] == "workfile" + ) if not is_workfile: continue From 2933b37ef7711aac4e04284120b6c9b0ce2c9612 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:34:27 +0200 Subject: [PATCH 122/513] Refactor code for readability --- .../deadline/abstract_submit_deadline.py | 124 ++++++++++-------- 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 86eebc0d35..46baa9ee57 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -543,72 +543,84 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): published. """ - anatomy = self._instance.context.data['anatomy'] - file_path = None - for i in self._instance.context: - is_workfile = ( - "workfile" in i.data.get("families", []) or - i.data["family"] == "workfile" - ) - if not is_workfile: - continue + def _get_workfile_instance(context): + """Find workfile instance in context""" + for i in context: - # test if there is instance of workfile waiting - # to be published. - assert i.data["publish"] is True, ( - "Workfile (scene) must be published along") - # determine published path from Anatomy. - template_data = i.data.get("anatomyData") - rep = i.data.get("representations")[0] - template_data["representation"] = rep.get("name") - template_data["ext"] = rep.get("ext") - template_data["comment"] = None - anatomy_filled = anatomy.format(template_data) - template_filled = anatomy_filled["publish"]["path"] - file_path = os.path.normpath(template_filled) + is_workfile = ( + "workfile" in i.data.get("families", []) or + i.data["family"] == "workfile" + ) + if not is_workfile: + continue - self.log.info("Using published scene for render {}".format( - file_path)) + # test if there is instance of workfile waiting + # to be published. + assert i.data["publish"] is True, ( + "Workfile (scene) must be published along") - if not os.path.exists(file_path): - self.log.error("published scene does not exist!") - raise + return i - if not replace_in_path: - return file_path + instance = self._instance + workfile_instance = _get_workfile_instance(instance.context) + if not workfile_instance: + return - # now we need to switch scene in expected files - # because token will now point to published - # scene file and that might differ from current one - new_scene = os.path.splitext(os.path.basename(file_path))[0] - orig_scene = os.path.splitext(os.path.basename( - self._instance.context.data["currentFile"]))[0] - exp = self._instance.data.get("expectedFiles") + # determine published path from Anatomy. + template_data = workfile_instance.data.get("anatomyData") + rep = workfile_instance.data.get("representations")[0] + template_data["representation"] = rep.get("name") + template_data["ext"] = rep.get("ext") + template_data["comment"] = None - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - new_exp = {} - for aov, files in exp[0].items(): - replaced_files = [] - for f in files: - replaced_files.append( - str(f).replace(orig_scene, new_scene) - ) - new_exp[aov] = replaced_files - # [] might be too much here, TODO - self._instance.data["expectedFiles"] = [new_exp] - else: - new_exp = [] - for f in exp: - new_exp.append( + anatomy = instance.context.data['anatomy'] + anatomy_filled = anatomy.format(template_data) + template_filled = anatomy_filled["publish"]["path"] + file_path = os.path.normpath(template_filled) + + self.log.info("Using published scene for render {}".format(file_path)) + + if not os.path.exists(file_path): + self.log.error("published scene does not exist!") + raise + + if not replace_in_path: + return file_path + + # now we need to switch scene in expected files + # because token will now point to published + # scene file and that might differ from current one + def _clean_name(path): + return os.path.splitext(os.path.basename(path))[0] + + new_scene = _clean_name(file_path) + orig_scene = _clean_name(instance.context.data["currentFile"]) + expected_files = instance.data.get("expectedFiles") + + if isinstance(expected_files[0], dict): + # we have aovs and we need to iterate over them + new_exp = {} + for aov, files in expected_files[0].items(): + replaced_files = [] + for f in files: + replaced_files.append( str(f).replace(orig_scene, new_scene) ) - self._instance.data["expectedFiles"] = new_exp + new_exp[aov] = replaced_files + # [] might be too much here, TODO + instance.data["expectedFiles"] = [new_exp] + else: + new_exp = [] + for f in expected_files: + new_exp.append( + str(f).replace(orig_scene, new_scene) + ) + instance.data["expectedFiles"] = new_exp - self.log.info("Scene name was switched {} -> {}".format( - orig_scene, new_scene - )) + self.log.info("Scene name was switched {} -> {}".format( + orig_scene, new_scene + )) return file_path From c725ff5b42c2f3f248a6af8f835020c9efb23182 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:35:05 +0200 Subject: [PATCH 123/513] Move replacing in `publishRenderMetadataFolder` to abstract base class --- openpype/modules/deadline/abstract_submit_deadline.py | 6 ++++++ .../deadline/plugins/publish/submit_maya_deadline.py | 9 --------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 46baa9ee57..f56cf49f6d 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -618,6 +618,12 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): ) instance.data["expectedFiles"] = new_exp + metadata_folder = instance.data.get("publishRenderMetadataFolder") + if metadata_folder: + metadata_folder = metadata_folder.replace(orig_scene, + new_scene) + instance.data["publishRenderMetadataFolder"] = metadata_folder + self.log.info("Scene name was switched {} -> {}".format( orig_scene, new_scene )) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 07ed237c94..26c26a124c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -258,15 +258,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) self._patch_workfile(filepath, patches) - # todo: on self.use_published replace path for publishRenderMetadataFolder - # if instance.data.get("publishRenderMetadataFolder"): - # instance.data["publishRenderMetadataFolder"] = \ - # instance.data["publishRenderMetadataFolder"].replace( - # orig_scene, new_scene) - # self.log.info("Scene name was switched {} -> {}".format( - # orig_scene, new_scene - # )) - # Gather needed data ------------------------------------------------ workspace = context.data["workspaceDir"] default_render_file = instance.context.data.get('project_settings')\ From 23e652a51f41a6c65d6feba6edb0268f85feccb8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:54:36 +0200 Subject: [PATCH 124/513] Patch plug-in payload with settings --- .../maya/plugins/publish/collect_render.py | 1 + .../plugins/publish/submit_maya_deadline.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index ebda5e190d..768a53329f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -293,6 +293,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "source": filepath, "expectedFiles": full_exp_files, "publishRenderMetadataFolder": common_publish_meta_path, + "renderProducts": layer_render_products, "resolutionWidth": lib.get_attr_in_layer( "defaultResolution.width", layer=layer_name ), diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 26c26a124c..854a66eaa5 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -215,16 +215,21 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): renderlayer = instance.data['setMembers'] # rs_beauty - self.payload_skeleton["PluginInfo"]["RenderLayer"] = renderlayer - self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = instance.data.get("renderSetupIncludeLights") # noqa - # Output driver to render plugin_info = DeadlinePluginInfo( SceneFile=context.data["currentFile"], Version=cmds.about(version=True), + RenderLayer=renderlayer, + RenderSetupIncludeLights=instance.data.get("renderSetupIncludeLights") # noqa ) - return attr.asdict(plugin_info) + plugin_payload = attr.asdict(plugin_info) + + # Patching with pluginInfo from settings + for key, value in self.pluginInfo.items(): + plugin_payload[key] = value + + return plugin_payload def process_submission(self): @@ -338,10 +343,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) - # add jobInfo and pluginInfo variables from Settings - payload["JobInfo"].update(self.jobInfo) - payload["PluginInfo"].update(self.pluginInfo) - if instance.data.get("tileRendering"): # Prepare tiles data self._tile_render(instance, payload) From 4abddd027de9d4a1814ddff1b971bb9a99c47008 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:55:07 +0200 Subject: [PATCH 125/513] Use collected render products for image prefix --- .../deadline/plugins/publish/submit_maya_deadline.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 854a66eaa5..bb7ae380b6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -307,7 +307,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "jobname": jobname, "comment": comment, "output_filename_0": output_filename_0, - "render_variables": render_variables, "renderlayer": renderlayer, "workspace": workspace, "dirname": dirname, @@ -564,6 +563,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): renderer = self._instance.data["renderer"] + # Get layer prefix + render_products = self._instance.data["renderProducts"] + layer_metadata = render_products.layer_data + layer_prefix = layer_metadata.filePrefix + # This hack is here because of how Deadline handles Renderman version. # it considers everything with `renderman` set as version older than # Renderman 22, and so if we are using renderman > 21 we need to set @@ -583,7 +587,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "SceneFile": data["filepath"], # Output directory and filename "OutputFilePath": data["dirname"].replace("\\", "/"), - "OutputFilePrefix": data["render_variables"]["filename_prefix"], # noqa: E501 + "OutputFilePrefix": layer_prefix, # Only render layers are considered renderable in this pipeline "UsingRenderLayers": True, From 6f5fcecfae7ec92bf2df80fb673bfff3e1049231 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:55:24 +0200 Subject: [PATCH 126/513] Use existing variable `renderer` --- .../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 bb7ae380b6..c7f91905ea 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -573,7 +573,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Renderman 22, and so if we are using renderman > 21 we need to set # renderer string on the job to `renderman22`. We will have to change # this when Deadline releases new version handling this. - if self._instance.data["renderer"] == "renderman": + if renderer == "renderman": try: from rfm2.config import cfg # noqa except ImportError: From ae250c4a100dcc4474512913dbc858326ced3c8a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:55:40 +0200 Subject: [PATCH 127/513] Remove unused `comment` key-value --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 - 1 file changed, 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 c7f91905ea..b2b877ab0e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -305,7 +305,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "filename": filename, "filepath": filepath, "jobname": jobname, - "comment": comment, "output_filename_0": output_filename_0, "renderlayer": renderlayer, "workspace": workspace, From 7af7f71edacea21a00c370e4ad1e92b2fe576b66 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 19:58:04 +0200 Subject: [PATCH 128/513] Remove logging of plugin name --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index b2b877ab0e..db796f25a9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -335,9 +335,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if export_job: payload["JobInfo"]["JobDependency0"] = export_job - plugin = payload["JobInfo"]["Plugin"] - self.log.info("using render plugin : {}".format(plugin)) - # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) From f91e33c0385762a7a73cca192f4d36716377ee1e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 23:21:30 +0200 Subject: [PATCH 129/513] More refactoring/cleanup (WIP) --- .../plugins/publish/submit_maya_deadline.py | 304 +++++++----------- 1 file changed, 110 insertions(+), 194 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index db796f25a9..8f12a9518f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -70,7 +70,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): tile_assembler_plugin = "OpenPypeTileAssembler" priority = 50 tile_priority = 50 - limit_groups = [] + limit = [] # limit groups jobInfo = {} pluginInfo = {} group = "none" @@ -112,23 +112,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.ChunkSize = instance.data.get("chunkSize", 10) job_info.Comment = context.data.get("comment") job_info.Priority = instance.data.get("priority", self.priority) + job_info.FramesPerTask = instance.data.get("framesPerTask", 1) if self.group != "none" and self.group: job_info.Group = self.group - if self.limit_groups: - job_info.LimitGroups = ",".join(self.limit_groups) + if self.limit: + job_info.LimitGroups = ",".join(self.limit) - # Optional, enable double-click to preview rendered - # frames from Deadline Monitor - self.payload_skeleton["JobInfo"]["OutputDirectory0"] = \ - os.path.dirname(output_filename_0).replace("\\", "/") - self.payload_skeleton["JobInfo"]["OutputFilename0"] = \ - output_filename_0.replace("\\", "/") - - # Add options from RenderGlobals------------------------------------- + # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) - self.payload_skeleton["JobInfo"].update(render_globals) + for key, value in render_globals: + setattr(job_info, key, value) keys = [ "FTRACK_API_KEY", @@ -140,7 +135,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "AVALON_TASK", "AVALON_APP_NAME", "OPENPYPE_DEV", - "OPENPYPE_LOG_NO_COLORS", "OPENPYPE_VERSION" ] # Add mongo url if it's enabled @@ -150,10 +144,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - # TODO: Taken from old publish class - test whether still needed environment["OPENPYPE_LOG_NO_COLORS"] = "1" - environment["OPENPYPE_MAYA_VERSION"] = cmds.about(v=True) # to recognize job from PYPE for turning Event On/Off environment["OPENPYPE_RENDER_JOB"] = "1" @@ -166,7 +158,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) # to recognize job from PYPE for turning Event On/Off job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + job_info.EnvironmentKeyValue = "OPENPYPE_LOG_NO_COLORS=1" + # Optional, enable double-click to preview rendered + # frames from Deadline Monitor for i, filepath in enumerate(instance.data["files"]): dirname = os.path.dirname(filepath) fname = os.path.basename(filepath) @@ -213,14 +208,13 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - renderlayer = instance.data['setMembers'] # rs_beauty - - # Output driver to render plugin_info = DeadlinePluginInfo( - SceneFile=context.data["currentFile"], + SceneFile=self.scene_path, Version=cmds.about(version=True), - RenderLayer=renderlayer, - RenderSetupIncludeLights=instance.data.get("renderSetupIncludeLights") # noqa + RenderLayer=instance.data['setMembers'], + RenderSetupIncludeLights=instance.data.get("renderSetupIncludeLights"), # noqa + ProjectPath=context.data["workspaceDir"], + UsingRenderLayers=True, ) plugin_payload = attr.asdict(plugin_info) @@ -236,12 +230,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - # Generated by AbstractSubmitDeadline. The `job_info`, `plugin_info` - # and `aux_files` are the skeleton payloads that are the basis for - # all the maya submissions - job_info = self.job_info - plugin_info = self.plugin_info - aux_files = self.aux_files filepath = self.scene_path # publish if `use_publish` else workfile # TODO: Avoid the need for this logic here, needed for submit publish @@ -250,18 +238,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance.data["outputDir"] = output_dir instance.data["toBeRenderedOn"] = "deadline" - self.limit_groups = self.limit - # Patch workfile (only when use_published is enabled) if self.use_published: - patches = ( - context.data["project_settings"].get( - "deadline", {}).get( - "publish", {}).get( - "MayaSubmitDeadline", {}).get( - "scene_patches", {}) - ) - self._patch_workfile(filepath, patches) + self._patch_workfile() # Gather needed data ------------------------------------------------ workspace = context.data["workspaceDir"] @@ -271,22 +250,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): .get('default_render_image_folder') filename = os.path.basename(filepath) dirname = os.path.join(workspace, default_render_file) - renderlayer = instance.data['setMembers'] # rs_beauty - - # Get the variables depending on the renderer - # TODO: Find replacement logic for `get_renderer_variables` through - # what is collected for the render or is implemented in maya - # api `lib_renderproducts` - render_variables = get_renderer_variables(renderlayer, dirname) - filename_0 = render_variables["filename_0"] - if self.use_published: - new_scene = os.path.splitext(filename)[0] - orig_scene = os.path.splitext( - os.path.basename(context.data["currentFile"]))[0] - filename_0 = render_variables["filename_0"].replace( - orig_scene, new_scene) - - output_filename_0 = filename_0 # this is needed because renderman handles directory and file # prefixes separately @@ -301,16 +264,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): pass # Fill in common data to payload ------------------------------------ + # TODO: Replace these with collected data from CollectRender payload_data = { "filename": filename, "filepath": filepath, - "jobname": jobname, "output_filename_0": output_filename_0, "renderlayer": renderlayer, - "workspace": workspace, "dirname": dirname, } + # Store output dir for unified publisher (filesequence) + instance.data["outputDir"] = os.path.dirname(output_filename_0) + # Submit preceding export jobs ------------------------------------- export_job = None assert not all(x in instance.data["families"] @@ -333,17 +298,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Add export job as dependency -------------------------------------- if export_job: - payload["JobInfo"]["JobDependency0"] = export_job - - # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname(output_filename_0) + job_info, _ = payload + job_info.JobDependency = export_job if instance.data.get("tileRendering"): # Prepare tiles data self._tile_render(instance, payload) else: # Submit main render job - self.submit(payload) + job_info, plugin_info = payload + self.submit(self.assemble_payload(job_info, plugin_info)) def _tile_render(self, instance, payload): @@ -546,18 +510,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance.data["jobBatchName"])) def _get_maya_payload(self, data): - payload = copy.deepcopy(self.payload_skeleton) - if not self.asset_dependencies: - job_info_ext = {} + job_info = copy.deepcopy(self.job_info) - else: - job_info_ext = { - # Asset dependency to wait for at least the scene file to sync. - "AssetDependency0": data["filepath"], - } - - renderer = self._instance.data["renderer"] + if self.asset_dependencies: + # Asset dependency to wait for at least the scene file to sync. + job_info.AssetDependency = self.scene_path # Get layer prefix render_products = self._instance.data["renderProducts"] @@ -569,6 +527,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Renderman 22, and so if we are using renderman > 21 we need to set # renderer string on the job to `renderman22`. We will have to change # this when Deadline releases new version handling this. + renderer = self._instance.data["renderer"] if renderer == "renderman": try: from rfm2.config import cfg # noqa @@ -580,29 +539,20 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): renderer = "renderman22" plugin_info = { - "SceneFile": data["filepath"], # Output directory and filename "OutputFilePath": data["dirname"].replace("\\", "/"), "OutputFilePrefix": layer_prefix, - - # Only render layers are considered renderable in this pipeline - "UsingRenderLayers": True, - - # Render only this layer - "RenderLayer": data["renderlayer"], - - # Determine which renderer to use from the file itself - "Renderer": renderer, - - # Resolve relative references - "ProjectPath": data["workspace"], } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info) - return payload + + return job_info, plugin_info def _get_vray_export_payload(self, data): - payload = copy.deepcopy(self.payload_skeleton) + + job_info = copy.deepcopy(self.job_info) + + job_info.Name = self._job_info_label("Export") + + # Get V-Ray settings info to compute output path vray_settings = cmds.ls(type="VRaySettingsNode") node = vray_settings[0] template = cmds.getAttr("{}.vrscene_filename".format(node)) @@ -610,34 +560,15 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): first_file = self.format_vray_output_filename(scene, template) first_file = "{}/{}".format(data["workspace"], first_file) output = os.path.dirname(first_file) - job_info_ext = { - # Job name, as seen in Monitor - "Name": "Export {} [{}-{}]".format( - data["jobname"], - int(self._instance.data["frameStartHandle"]), - int(self._instance.data["frameEndHandle"])), - "Plugin": self._instance.data.get( - "mayaRenderPlugin", "MayaPype"), - "FramesPerTask": self._instance.data.get("framesPerTask", 1) - } - - plugin_info_ext = { - # Renderer + plugin_info = { "Renderer": "vray", - # Input - "SceneFile": data["filepath"], "SkipExistingFrames": True, - "UsingRenderLayers": True, "UseLegacyRenderLayers": True, - "RenderLayer": data["renderlayer"], - "ProjectPath": data["workspace"], "OutputFilePath": output } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info_ext) - return payload + return job_info, plugin_info def _get_arnold_export_payload(self, data): @@ -653,76 +584,55 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): script = os.path.normpath(module_path) - payload = copy.deepcopy(self.payload_skeleton) - job_info_ext = { - # Job name, as seen in Monitor - "Name": "Export {} [{}-{}]".format( - data["jobname"], - int(self._instance.data["frameStartHandle"]), - int(self._instance.data["frameEndHandle"])), + job_info = copy.deepcopy(self.job_info) + plugin_info = copy.deepcopy(self.plugin_info) - "Plugin": "Python", - "FramesPerTask": self._instance.data.get("framesPerTask", 1), - "Frames": 1 + job_info.Name = self._job_info_label("Export") + + # Force a single frame Python job + job_info.Plugin = "Python" + job_info.Frames = 1 + + # add required env vars for the export script + envs = { + "AVALON_APP_NAME": os.environ.get("AVALON_APP_NAME"), + "OPENPYPE_ASS_EXPORT_RENDER_LAYER": data["renderlayer"], + "OPENPYPE_ASS_EXPORT_SCENE_FILE": self.scene_path, + "OPENPYPE_ASS_EXPORT_OUTPUT": payload['JobInfo']['OutputFilename0'], # noqa + "OPENPYPE_ASS_EXPORT_START": int(self._instance.data["frameStartHandle"]), # noqa + "OPENPYPE_ASS_EXPORT_END": int(self._instance.data["frameEndHandle"]), # noqa + "OPENPYPE_ASS_EXPORT_STEP": 1 } + for key, value in envs.items(): + job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, + value=value) - plugin_info_ext = { + plugin_info.update({ "Version": "3.6", "ScriptFile": script, "Arguments": "", "SingleFrameOnly": "True", - } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info_ext) + }) - envs = [ - v - for k, v in payload["JobInfo"].items() - if k.startswith("EnvironmentKeyValue") - ] - - # add app name to environment - envs.append( - "AVALON_APP_NAME={}".format(os.environ.get("AVALON_APP_NAME"))) - envs.append( - "OPENPYPE_ASS_EXPORT_RENDER_LAYER={}".format(data["renderlayer"])) - envs.append( - "OPENPYPE_ASS_EXPORT_SCENE_FILE={}".format(data["filepath"])) - envs.append( - "OPENPYPE_ASS_EXPORT_OUTPUT={}".format( - payload['JobInfo']['OutputFilename0'])) - envs.append( - "OPENPYPE_ASS_EXPORT_START={}".format( - int(self._instance.data["frameStartHandle"]))) - envs.append( - "OPENPYPE_ASS_EXPORT_END={}".format( - int(self._instance.data["frameEndHandle"]))) - envs.append( - "OPENPYPE_ASS_EXPORT_STEP={}".format(1)) - - for i, e in enumerate(envs): - payload["JobInfo"]["EnvironmentKeyValue{}".format(i)] = e - return payload + return job_info, plugin_info def _get_vray_render_payload(self, data): - payload = copy.deepcopy(self.payload_skeleton) + + # Job Info + job_info = copy.deepcopy(self.job_info) + job_info.Name = self._job_info_label("Render") + job_info.Plugin = "Vray" + job_info.OverrideTaskExtraInfoNames = False + + # Plugin Info vray_settings = cmds.ls(type="VRaySettingsNode") node = vray_settings[0] template = cmds.getAttr("{}.vrscene_filename".format(node)) # "vrayscene//_/" - scene, _ = os.path.splitext(data["filename"]) + scene, _ = os.path.splitext(self.scene_path) first_file = self.format_vray_output_filename(scene, template) first_file = "{}/{}".format(data["workspace"], first_file) - job_info_ext = { - "Name": "Render {} [{}-{}]".format( - data["jobname"], - int(self._instance.data["frameStartHandle"]), - int(self._instance.data["frameEndHandle"])), - - "Plugin": "Vray", - "OverrideTaskExtraInfoNames": False, - } plugin_info = { "InputFilename": first_file, @@ -731,35 +641,28 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "Width": self._instance.data["resolutionWidth"], "Height": self._instance.data["resolutionHeight"], - "OutputFilePath": payload["JobInfo"]["OutputDirectory0"], - "OutputFileName": payload["JobInfo"]["OutputFilename0"] + "OutputFilePath": job_info.OutputDirectory[0], + "OutputFileName": job_info.OutputFilename[0] } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info) - return payload + return job_info, plugin_info def _get_arnold_render_payload(self, data): - payload = copy.deepcopy(self.payload_skeleton) + + # Job Info + job_info = copy.deepcopy(self.job_info) + job_info.Name = self._job_info_label("Render") + job_info.Plugin = "Arnold" + job_info.OverrideTaskExtraInfoNames = False + + # Plugin Info ass_file, _ = os.path.splitext(data["output_filename_0"]) first_file = ass_file + ".ass" - job_info_ext = { - "Name": "Render {} [{}-{}]".format( - data["jobname"], - int(self._instance.data["frameStartHandle"]), - int(self._instance.data["frameEndHandle"])), - - "Plugin": "Arnold", - "OverrideTaskExtraInfoNames": False, - } - plugin_info = { "ArnoldFile": first_file, } - payload["JobInfo"].update(job_info_ext) - payload["PluginInfo"].update(plugin_info) - return payload + return job_info, plugin_info def format_vray_output_filename(self, filename, template, dir=False): """Format the expected output file of the Export job. @@ -804,7 +707,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): return result - def _patch_workfile(self, file, patches): + def _patch_workfile(self): # type: (str, dict) -> [str, None] """Patch Maya scene. @@ -818,19 +721,25 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "line": "line to insert" } - Args: - file (str): File to patch. - patches (dict): Dictionary defining patches. - - Returns: - str: Patched file path or None - """ - if not patches or os.path.splitext(file)[1].lower() != ".ma": + project_settings = self._instance.context.data["project_settings"] + patches = ( + project_settings.get( + "deadline", {}).get( + "publish", {}).get( + "MayaSubmitDeadline", {}).get( + "scene_patches", {}) + ) + if not patches: + return + + if not os.path.splitext(self.scene_path)[1].lower() != ".ma": + self.log.debug("Skipping workfile patch since workfile is not " + ".ma file") return compiled_regex = [re.compile(p["regex"]) for p in patches] - with open(file, "r+") as pf: + with open(self.scene_path, "r+") as pf: scene_data = pf.readlines() for ln, line in enumerate(scene_data): for i, r in enumerate(compiled_regex): @@ -839,10 +748,17 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): pf.seek(0) pf.writelines(scene_data) pf.truncate() - self.log.info( - "Applied {} patch to scene.".format( - patches[i]["name"])) - return file + self.log.info("Applied {} patch to scene.".format( + patches[i]["name"] + )) + + def _job_info_label(self, label): + return "{label} {job.Name} [{start}-{end}]".format( + label=label, + job=self.job_info, + start=int(self._instance.data["frameStartHandle"]), + end=int(self._instance.data["frameEndHandle"]), + ) def _format_tiles( From f9bbda244bee373dd3bfb025528923d061808525 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 23:22:19 +0200 Subject: [PATCH 130/513] More explicit PluginInfo name --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 8f12a9518f..87ef4e6db9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -38,7 +38,7 @@ from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo @attr.s -class DeadlinePluginInfo(): +class MayaPluginInfo: SceneFile = attr.ib(default=None) # Input OutputFilePath = attr.ib(default=None) # Output directory and filename OutputFilePrefix = attr.ib(default=None) @@ -208,7 +208,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - plugin_info = DeadlinePluginInfo( + plugin_info = MayaPluginInfo( SceneFile=self.scene_path, Version=cmds.about(version=True), RenderLayer=instance.data['setMembers'], From ecf2a89081f19e14c65b0fd7b1992fe80519e983 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Aug 2022 23:39:11 +0200 Subject: [PATCH 131/513] More temp restructuring --- .../plugins/publish/submit_maya_deadline.py | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 87ef4e6db9..a77ccd73d4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -273,9 +273,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "dirname": dirname, } - # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname(output_filename_0) - # Submit preceding export jobs ------------------------------------- export_job = None assert not all(x in instance.data["families"] @@ -326,26 +323,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): plugin_info["ImageWidth"] = instance.data.get("resolutionWidth") plugin_info["RegionRendering"] = True - assembly_payload = { - "AuxFiles": [], - "JobInfo": { - "BatchName": payload["JobInfo"]["BatchName"], - "Frames": 1, - "Name": "{} - Tile Assembly Job".format( - payload["JobInfo"]["Name"]), - "OutputDirectory0": - payload["JobInfo"]["OutputDirectory0"].replace( - "\\", "/"), - "Plugin": self.tile_assembler_plugin, - "MachineLimit": 1 - }, - "PluginInfo": { + assembly_job_info = copy.deepcopy(job_info) + assembly_job_info.Plugin = self.tile_assembler_plugin + assembly_job_info.Name = "{job.Name} - Tile Assembly Job".format( + job=job_info) + assembly_job_info.Frames = 1 + assembly_job_info.MachineLimit = 1 + assembly_job_info.Priority = instance.data.get("tile_priority", + self.tile_priority) + + assembly_plugin_info = { "CleanupTiles": 1, "ErrorOnMissing": True - } } - assembly_payload["JobInfo"]["Priority"] = self._instance.data.get( - "tile_priority", self.tile_priority) frame_payloads = [] assembly_payloads = [] @@ -414,6 +404,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): file_index = 1 for file in assembly_files: frame = re.search(R_FRAME_NUMBER, file).group("frame") + new_assembly_payload = copy.deepcopy(assembly_payload) new_assembly_payload["JobInfo"]["Name"] = \ "{} (Frame {})".format( @@ -434,7 +425,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.info( "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) - url = "{}/api/jobs".format(self.deadline_url) tiles_count = instance.data.get("tilesX") * instance.data.get( "tilesY") # noqa: E501 @@ -444,9 +434,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_id = response.json()["_id"] hash = response.json()["Props"]["Ex0"] + # Add assembly job dependencies for assembly_job in assembly_payloads: - if assembly_job["JobInfo"]["ExtraInfo0"] == hash: - assembly_job["JobInfo"]["JobDependency0"] = job_id + assembly_job_info = assembly_job["JobInfo"] + if assembly_job_info.ExtraInfo[0] == hash: + assembly_job.JobDependency = job_id for assembly_job in assembly_payloads: file = assembly_job["JobInfo"]["ExtraInfo1"] @@ -461,14 +453,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): ) ) + config_file_dir = os.path.dirname(config_file) try: - if not os.path.isdir(os.path.dirname(config_file)): - os.makedirs(os.path.dirname(config_file)) + if not os.path.isdir(config_file_dir): + os.makedirs(config_file_dir) except OSError: # directory is not available - self.log.warning( - "Path is unreachable: `{}`".format( - os.path.dirname(config_file))) + self.log.warning("Path is unreachable: " + "`{}`".format(config_file_dir)) # add config file as job auxFile assembly_job["AuxFiles"] = [config_file] @@ -505,10 +497,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): response.json()["_id"]) job_idx += 1 - instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] - self.log.info("Setting batch name on instance: {}".format( - instance.data["jobBatchName"])) - def _get_maya_payload(self, data): job_info = copy.deepcopy(self.job_info) From 6abafd0aca1ca06204f5e5bc11907a0a6a855900 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 00:41:30 +0200 Subject: [PATCH 132/513] Refactor tile logic --- .../plugins/publish/submit_maya_deadline.py | 120 ++++++++---------- 1 file changed, 55 insertions(+), 65 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index a77ccd73d4..920adf7e4a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -314,11 +314,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # if we have sequence of files, we need to create tile job for # every frame - job_info.TileJob = True job_info.TileJobTilesInX = instance.data.get("tilesX") job_info.TileJobTilesInY = instance.data.get("tilesY") + tiles_count = job_info.TileJobTilesInX * job_info.TileJobTilesInY + plugin_info["ImageHeight"] = instance.data.get("resolutionHeight") plugin_info["ImageWidth"] = instance.data.get("resolutionWidth") plugin_info["RegionRendering"] = True @@ -334,7 +335,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): assembly_plugin_info = { "CleanupTiles": 1, - "ErrorOnMissing": True + "ErrorOnMissing": True, + "Renderer": self._instance.data["renderer"] } frame_payloads = [] @@ -367,81 +369,69 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): file_index = 1 for file in files: frame = re.search(R_FRAME_NUMBER, file).group("frame") - new_payload = copy.deepcopy(payload) - new_payload["JobInfo"]["Name"] = \ - "{} (Frame {} - {} tiles)".format( + + new_job_info = copy.deepcopy(job_info) + new_job_info.Name = "{} (Frame {} - {} tiles)".format( payload["JobInfo"]["Name"], frame, instance.data.get("tilesX") * instance.data.get("tilesY") - # noqa: E501 - ) - self.log.info( - "... preparing job {}".format( - new_payload["JobInfo"]["Name"])) - new_payload["JobInfo"]["TileJobFrame"] = frame + ) + new_job_info.TileJobFrame = frame - tiles_data = _format_tiles( + new_plugin_info = copy.deepcopy(plugin_info) + + # Add tile data into job info and plugin info + tiles_out, _ = _format_tiles( file, 0, instance.data.get("tilesX"), instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), payload["PluginInfo"]["OutputFilePrefix"] - )[0] - new_payload["JobInfo"].update(tiles_data["JobInfo"]) - new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) + ) + new_job_info.update(tiles_out["JobInfo"]) + new_plugin_info.update(tiles_out["PluginInfo"]) self.log.info("hashing {} - {}".format(file_index, file)) job_hash = hashlib.sha256( ("{}_{}".format(file_index, file)).encode("utf-8")) frame_jobs[frame] = job_hash.hexdigest() - new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() - new_payload["JobInfo"]["ExtraInfo1"] = file - frame_payloads.append(new_payload) - file_index += 1 + new_job_info.ExtraInfo[0] = job_hash.hexdigest() + new_job_info.ExtraInfo[1] = file - file_index = 1 - for file in assembly_files: - frame = re.search(R_FRAME_NUMBER, file).group("frame") - - new_assembly_payload = copy.deepcopy(assembly_payload) - new_assembly_payload["JobInfo"]["Name"] = \ - "{} (Frame {})".format( - assembly_payload["JobInfo"]["Name"], - frame) - new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( - REPL_FRAME_NUMBER, - "\\1{}\\3".format("#" * len(frame)), file) - - new_assembly_payload["PluginInfo"]["Renderer"] = \ - self._instance.data["renderer"] # noqa: E501 - new_assembly_payload["JobInfo"]["ExtraInfo0"] = frame_jobs[ - frame] # noqa: E501 - new_assembly_payload["JobInfo"]["ExtraInfo1"] = file - assembly_payloads.append(new_assembly_payload) + frame_payloads.append(self.assemble_payload( + job_info=new_job_info, + plugin_info=new_plugin_info + )) file_index += 1 self.log.info( "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) - tiles_count = instance.data.get("tilesX") * instance.data.get( - "tilesY") # noqa: E501 - - for tile_job in frame_payloads: - response = self.submit(tile_job) - + frame_tile_job_id = {} + for tile_job_payload in frame_payloads: + response = self.submit(tile_job_payload) job_id = response.json()["_id"] - hash = response.json()["Props"]["Ex0"] + frame_tile_job_id[frame] = job_id - # Add assembly job dependencies - for assembly_job in assembly_payloads: - assembly_job_info = assembly_job["JobInfo"] - if assembly_job_info.ExtraInfo[0] == hash: - assembly_job.JobDependency = job_id + assembly_jobs = [] + for i, file in enumerate(assembly_files): + frame = re.search(R_FRAME_NUMBER, file).group("frame") + + frame_assembly_job_info = copy.deepcopy(assembly_job_info) + frame_assembly_job_info.Name += " (Frame {})".format(frame) + frame_assembly_job_info.OutputFilename[0] = re.sub( + REPL_FRAME_NUMBER, + "\\1{}\\3".format("#" * len(frame)), file) + + hash = frame_jobs[frame] + tile_job_id = frame_tile_job_id[frame] + + frame_assembly_job_info.ExtraInfo[0] = hash + frame_assembly_job_info.ExtraInfo[1] = file + frame_assembly_job_info.JobDependency = tile_job_id - for assembly_job in assembly_payloads: - file = assembly_job["JobInfo"]["ExtraInfo1"] # write assembly job config files now = datetime.now() @@ -462,9 +452,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.warning("Path is unreachable: " "`{}`".format(config_file_dir)) - # add config file as job auxFile - assembly_job["AuxFiles"] = [config_file] - with open(config_file, "w") as cf: print("TileCount={}".format(tiles_count), file=cf) print("ImageFileName={}".format(file), file=cf) @@ -485,17 +472,20 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for k, v in tiles.items(): print("{}={}".format(k, v), file=cf) - job_idx = 1 - instance.data["assemblySubmissionJobs"] = [] - for ass_job in assembly_payloads: - self.log.info("submitting assembly job {} of {}".format( - job_idx, len(assembly_payloads) - )) - response = self.submit(ass_job) + payload = self.assemble_payload( + job_info=frame_assembly_job_info, + plugin_info=assembly_plugin_info.copy(), + # add config file as job auxFile + aux_files=[config_file] + ) - instance.data["assemblySubmissionJobs"].append( - response.json()["_id"]) - job_idx += 1 + self.log.info("submitting assembly job {} of {}".format( + i+1, len(assembly_payloads) + )) + response = self.submit(payload) + assembly_jobs.append(response.json()["_id"]) + + instance.data["assemblySubmissionJobs"] = assembly_jobs def _get_maya_payload(self, data): From a6002de641e1ad500192be433f620f17680ea056 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 00:58:23 +0200 Subject: [PATCH 133/513] Refactor _format_tiles for readability --- .../plugins/publish/submit_maya_deadline.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 920adf7e4a..00d8eb7859 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -800,23 +800,23 @@ def _format_tiles( tiles_x, tiles_y ) - out_tile_index = "OutputFilename{}Tile{}".format( - str(index), tile - ) + + # Job Info new_filename = "{}/{}{}".format( os.path.dirname(filename), tile_prefix, os.path.basename(filename) ) - out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(tile)] = \ - "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) + out["JobInfo"]["OutputFilename{}Tile{}".format(index, tile)] = new_filename # noqa + # Plugin Info + out["PluginInfo"]["RegionPrefix{}".format(tile)] = "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) # noqa: E501 out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + # Tile config cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename @@ -828,4 +828,5 @@ def _format_tiles( cfg["Tile{}Height".format(tile)] = h_space tile += 1 + return out, cfg From a9fe806fec1a5e5ecf98327cfa4845b8b6d3edc0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 01:02:24 +0200 Subject: [PATCH 134/513] Calculate once --- .../plugins/publish/submit_maya_deadline.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 00d8eb7859..d0348119dc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -800,6 +800,10 @@ def _format_tiles( tiles_x, tiles_y ) + top = int(height) - (tile_y * h_space) + bottom = int(height) - ((tile_y - 1) * h_space) - 1 + left = (tile_x - 1) * w_space + right = (tile_x * w_space) - 1 # Job Info new_filename = "{}/{}{}".format( @@ -811,19 +815,17 @@ def _format_tiles( # Plugin Info out["PluginInfo"]["RegionPrefix{}".format(tile)] = "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) # noqa: E501 - out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 - out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 - out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 - out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + out["PluginInfo"]["RegionTop{}".format(tile)] = top + out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom + out["PluginInfo"]["RegionLeft{}".format(tile)] = left + out["PluginInfo"]["RegionRight{}".format(tile)] = right # Tile config cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename - cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space - - cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) - + cfg["Tile{}X".format(tile)] = left + cfg["Tile{}Y".format(tile)] = top cfg["Tile{}Width".format(tile)] = w_space cfg["Tile{}Height".format(tile)] = h_space From d7c72f97b30f85aca15b4a8148c140595f0b2a3a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 01:10:19 +0200 Subject: [PATCH 135/513] Batch submit assembly jobs --- .../plugins/publish/submit_maya_deadline.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index d0348119dc..265c0f79ec 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -339,7 +339,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "Renderer": self._instance.data["renderer"] } - frame_payloads = [] assembly_payloads = [] R_FRAME_NUMBER = re.compile( @@ -358,14 +357,15 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): itertools.chain.from_iterable( [f for _, f in exp[0].items()])) if not files: - # if beauty doesn't exists, use first aov we found + # if beauty doesn't exist, use first aov we found files = exp[0].get(list(exp[0].keys())[0]) else: files = exp assembly_files = files + # Define frame tile jobs frame_jobs = {} - + frame_payloads = {} file_index = 1 for file in files: frame = re.search(R_FRAME_NUMBER, file).group("frame") @@ -400,22 +400,24 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): new_job_info.ExtraInfo[0] = job_hash.hexdigest() new_job_info.ExtraInfo[1] = file - frame_payloads.append(self.assemble_payload( + frame_payloads[frame] = self.assemble_payload( job_info=new_job_info, plugin_info=new_plugin_info - )) + ) file_index += 1 self.log.info( "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) + # Submit frame tile jobs frame_tile_job_id = {} - for tile_job_payload in frame_payloads: + for frame, tile_job_payload in frame_payloads.items(): response = self.submit(tile_job_payload) job_id = response.json()["_id"] frame_tile_job_id[frame] = job_id - assembly_jobs = [] + # Define assembly payloads + assembly_payloads = [] for i, file in enumerate(assembly_files): frame = re.search(R_FRAME_NUMBER, file).group("frame") @@ -478,14 +480,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # add config file as job auxFile aux_files=[config_file] ) + assembly_payloads.append(payload) + # Submit assembly jobs + assembly_job_ids = [] + for i, payload in enumerate(assembly_payloads): self.log.info("submitting assembly job {} of {}".format( i+1, len(assembly_payloads) )) response = self.submit(payload) - assembly_jobs.append(response.json()["_id"]) + assembly_job_ids.append(response.json()["_id"]) - instance.data["assemblySubmissionJobs"] = assembly_jobs + instance.data["assemblySubmissionJobs"] = assembly_job_ids def _get_maya_payload(self, data): From 965522585b98e441907480caee57af5dad92c2d2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 01:11:12 +0200 Subject: [PATCH 136/513] Remove redundant docstring --- .../deadline/plugins/publish/submit_maya_deadline.py | 10 ---------- 1 file changed, 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 265c0f79ec..cd9f426977 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -51,16 +51,6 @@ class MayaPluginInfo: class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): - """Submit available render layers to Deadline. - - Renders are submitted to a Deadline Web Service as - supplied via settings key "DEADLINE_REST_URL". - - Attributes: - use_published (bool): Use published scene to render instead of the - one in work area. - - """ label = "Submit Render to Deadline" hosts = ["maya"] From 8af88e115723e6abc73fff279773043e4a520326 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 09:08:44 +0200 Subject: [PATCH 137/513] More cleanup --- .../plugins/publish/submit_maya_deadline.py | 98 +++++++------------ 1 file changed, 38 insertions(+), 60 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index cd9f426977..95140a082f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -86,8 +86,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.Name = "%s - %s" % (src_filename, instance.name) job_info.BatchName = src_filename job_info.Plugin = instance.data.get("mayaRenderPlugin", "MayaBatch") - job_info.UserName = context.data.get( - "deadlineUser", getpass.getuser()) + job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) # Deadline requires integers in frame range frames = "{start}-{end}x{step}".format( @@ -134,25 +133,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - # TODO: Taken from old publish class - test whether still needed - environment["OPENPYPE_LOG_NO_COLORS"] = "1" # to recognize job from PYPE for turning Event On/Off environment["OPENPYPE_RENDER_JOB"] = "1" + environment["OPENPYPE_LOG_NO_COLORS"] = "1" - for key in keys: - val = environment.get(key) - if val: - job_info.EnvironmentKeyValue = "{key}={value}".format( - key=key, - value=val - ) - # to recognize job from PYPE for turning Event On/Off - job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" - job_info.EnvironmentKeyValue = "OPENPYPE_LOG_NO_COLORS=1" + for key, value in environment.items(): + if not value: + continue + job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, + value=value) - # Optional, enable double-click to preview rendered - # frames from Deadline Monitor - for i, filepath in enumerate(instance.data["files"]): + # Enable double-click to preview rendered frames from Deadline Monitor + for filepath in instance.data["files"]: dirname = os.path.dirname(filepath) fname = os.path.basename(filepath) job_info.OutputDirectory = dirname.replace("\\", "/") @@ -241,25 +233,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): filename = os.path.basename(filepath) dirname = os.path.join(workspace, default_render_file) - # this is needed because renderman handles directory and file - # prefixes separately - if self._instance.data["renderer"] == "renderman": - dirname = os.path.dirname(output_filename_0) - - # Create render folder ---------------------------------------------- - try: - # Ensure render folder exists - os.makedirs(dirname) - except OSError: - pass - # Fill in common data to payload ------------------------------------ # TODO: Replace these with collected data from CollectRender payload_data = { "filename": filename, - "filepath": filepath, - "output_filename_0": output_filename_0, - "renderlayer": renderlayer, "dirname": dirname, } @@ -299,8 +276,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): def _tile_render(self, instance, payload): # As collected by super process() - job_info = self.job_info - plugin_info = self.pluginInfo + job_info = copy.deepcopy(self.job_info) + plugin_info = copy.deepcopy(self.plugin_info) # if we have sequence of files, we need to create tile job for # every frame @@ -314,23 +291,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): plugin_info["ImageWidth"] = instance.data.get("resolutionWidth") plugin_info["RegionRendering"] = True - assembly_job_info = copy.deepcopy(job_info) - assembly_job_info.Plugin = self.tile_assembler_plugin - assembly_job_info.Name = "{job.Name} - Tile Assembly Job".format( - job=job_info) - assembly_job_info.Frames = 1 - assembly_job_info.MachineLimit = 1 - assembly_job_info.Priority = instance.data.get("tile_priority", - self.tile_priority) - - assembly_plugin_info = { - "CleanupTiles": 1, - "ErrorOnMissing": True, - "Renderer": self._instance.data["renderer"] - } - - assembly_payloads = [] - R_FRAME_NUMBER = re.compile( r".+\.(?P[0-9]+)\..+") # noqa: N806, E501 REPL_FRAME_NUMBER = re.compile( @@ -407,7 +367,23 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): frame_tile_job_id[frame] = job_id # Define assembly payloads + assembly_job_info = copy.deepcopy(job_info) + assembly_job_info.Plugin = self.tile_assembler_plugin + assembly_job_info.Name = "{job.Name} - Tile Assembly Job".format( + job=job_info) + assembly_job_info.Frames = 1 + assembly_job_info.MachineLimit = 1 + assembly_job_info.Priority = instance.data.get("tile_priority", + self.tile_priority) + + assembly_plugin_info = { + "CleanupTiles": 1, + "ErrorOnMissing": True, + "Renderer": self._instance.data["renderer"] + } + assembly_payloads = [] + output_dir = self.job_info.OutputDirectory[0] for i, file in enumerate(assembly_files): frame = re.search(R_FRAME_NUMBER, file).group("frame") @@ -427,22 +403,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # write assembly job config files now = datetime.now() - config_file = os.path.join( - os.path.dirname(output_filename_0), + config_file = os.path.join(output_dir, "{}_config_{}.txt".format( os.path.splitext(file)[0], now.strftime("%Y_%m_%d_%H_%M_%S") ) ) - - config_file_dir = os.path.dirname(config_file) try: - if not os.path.isdir(config_file_dir): - os.makedirs(config_file_dir) + if not os.path.isdir(output_dir): + os.makedirs(output_dir) except OSError: # directory is not available self.log.warning("Path is unreachable: " - "`{}`".format(config_file_dir)) + "`{}`".format(output_dir)) with open(config_file, "w") as cf: print("TileCount={}".format(tiles_count), file=cf) @@ -567,17 +540,22 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.Plugin = "Python" job_info.Frames = 1 + renderlayer = self._instance.data["setMembers"] + # add required env vars for the export script envs = { "AVALON_APP_NAME": os.environ.get("AVALON_APP_NAME"), - "OPENPYPE_ASS_EXPORT_RENDER_LAYER": data["renderlayer"], + "OPENPYPE_ASS_EXPORT_RENDER_LAYER": renderlayer, "OPENPYPE_ASS_EXPORT_SCENE_FILE": self.scene_path, - "OPENPYPE_ASS_EXPORT_OUTPUT": payload['JobInfo']['OutputFilename0'], # noqa + "OPENPYPE_ASS_EXPORT_OUTPUT": job_info.OutputFilename[0], "OPENPYPE_ASS_EXPORT_START": int(self._instance.data["frameStartHandle"]), # noqa "OPENPYPE_ASS_EXPORT_END": int(self._instance.data["frameEndHandle"]), # noqa "OPENPYPE_ASS_EXPORT_STEP": 1 } for key, value in envs.items(): + if not value: + continue + job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, value=value) From e8aa926cb7d338427ce7ba558f8ffa1609fde8ef Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 09:28:42 +0200 Subject: [PATCH 138/513] Move single use of in-line function to the class for readability --- .../deadline/abstract_submit_deadline.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index f56cf49f6d..a3db3feac9 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -544,26 +544,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): """ - def _get_workfile_instance(context): - """Find workfile instance in context""" - for i in context: - - is_workfile = ( - "workfile" in i.data.get("families", []) or - i.data["family"] == "workfile" - ) - if not is_workfile: - continue - - # test if there is instance of workfile waiting - # to be published. - assert i.data["publish"] is True, ( - "Workfile (scene) must be published along") - - return i - instance = self._instance - workfile_instance = _get_workfile_instance(instance.context) + workfile_instance = self._get_workfile_instance(instance.context) if not workfile_instance: return @@ -689,3 +671,22 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): self._instance.data["deadlineSubmissionJob"] = result return result["_id"] + + @staticmethod + def _get_workfile_instance(context): + """Find workfile instance in context""" + for i in context: + + is_workfile = ( + "workfile" in i.data.get("families", []) or + i.data["family"] == "workfile" + ) + if not is_workfile: + continue + + # test if there is instance of workfile waiting + # to be published. + assert i.data["publish"] is True, ( + "Workfile (scene) must be published along") + + return i From c6a0a199e1810e5c63c484d5871d34d14594e5be Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 09:40:08 +0200 Subject: [PATCH 139/513] Cosmetics --- .../deadline/plugins/publish/submit_maya_deadline.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 95140a082f..873005e051 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -267,15 +267,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if instance.data.get("tileRendering"): # Prepare tiles data - self._tile_render(instance, payload) + self._tile_render(payload) else: # Submit main render job job_info, plugin_info = payload self.submit(self.assemble_payload(job_info, plugin_info)) - def _tile_render(self, instance, payload): + def _tile_render(self, payload): # As collected by super process() + instance = self._instance job_info = copy.deepcopy(self.job_info) plugin_info = copy.deepcopy(self.plugin_info) @@ -321,11 +322,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): frame = re.search(R_FRAME_NUMBER, file).group("frame") new_job_info = copy.deepcopy(job_info) - new_job_info.Name = "{} (Frame {} - {} tiles)".format( - payload["JobInfo"]["Name"], - frame, - instance.data.get("tilesX") * instance.data.get("tilesY") - ) + new_job_info.Name += " (Frame {} - {} tiles)".format(frame, + tiles_count) new_job_info.TileJobFrame = frame new_plugin_info = copy.deepcopy(plugin_info) From e429e2ec41d23c097b301c31485a38aea634d6a3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 10:19:38 +0200 Subject: [PATCH 140/513] Remove json dump since `renderProducts` are not serializable --- openpype/hosts/maya/plugins/publish/collect_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 768a53329f..14aac2f206 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -360,7 +360,6 @@ class CollectMayaRender(pyblish.api.ContextPlugin): instance.data["label"] = label instance.data["farm"] = True instance.data.update(data) - self.log.debug("data: {}".format(json.dumps(data, indent=4))) def parse_options(self, render_globals): """Get all overrides with a value, skip those without. From 9472cbe271af4be7c0328bf05200f44c521c58ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 11:23:14 +0200 Subject: [PATCH 141/513] Fix submission --- .../collect_deadline_server_from_instance.py | 2 +- .../plugins/publish/submit_maya_deadline.py | 71 +++++++++---------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index a7035cd99f..9981bead3e 100644 --- a/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -13,7 +13,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.415 label = "Deadline Webservice from the Instance" - families = ["rendering"] + families = ["rendering", "renderlayer"] def process(self, instance): instance.data["deadlineUrl"] = self._collect_deadline_url(instance) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 873005e051..2afa1883c4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -27,7 +27,6 @@ import itertools from collections import OrderedDict import attr -import clique from maya import cmds @@ -111,7 +110,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) - for key, value in render_globals: + for key, value in render_globals.items(): setattr(job_info, key, value) keys = [ @@ -143,13 +142,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, value=value) - # Enable double-click to preview rendered frames from Deadline Monitor - for filepath in instance.data["files"]: - dirname = os.path.dirname(filepath) - fname = os.path.basename(filepath) - job_info.OutputDirectory = dirname.replace("\\", "/") - job_info.OutputFilename = fname - # Adding file dependencies. if self.asset_dependencies: dependencies = instance.context.data["fileDependencies"] @@ -160,28 +152,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Add list of expected files to job # --------------------------------- exp = instance.data.get("expectedFiles") - - def _get_output_filename(files): - col, rem = clique.assemble(files) - if not col and rem: - # we couldn't find any collections but have - # individual files. - assert len(rem) == 1, ( - "Found multiple non related files " - "to render, don't know what to do " - "with them.") - return rem[0] - else: - return col[0].format('{head}{padding}{tail}') - - if isinstance(exp[0], dict): - # we have aovs and we need to iterate over them - for _aov, files in exp[0].items(): - output_file = _get_output_filename(files) - job_info.OutputFilename = output_file - else: - output_file = _get_output_filename(exp) - job_info.OutputFilename = output_file + for filepath in self._iter_expected_files(exp): + job_info.OutputDirectory = os.path.dirname(filepath) + job_info.OutputFilename = os.path.basename(filepath) return job_info @@ -194,6 +167,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): SceneFile=self.scene_path, Version=cmds.about(version=True), RenderLayer=instance.data['setMembers'], + Renderer=instance.data["renderer"], RenderSetupIncludeLights=instance.data.get("renderSetupIncludeLights"), # noqa ProjectPath=context.data["workspaceDir"], UsingRenderLayers=True, @@ -216,7 +190,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # TODO: Avoid the need for this logic here, needed for submit publish # Store output dir for unified publisher (filesequence) - output_dir = os.path.dirname(instance.data["files"][0]) + expected_files = instance.data["expectedFiles"] + first_file = next(self._iter_expected_files(expected_files)) + output_dir = os.path.dirname(first_file) instance.data["outputDir"] = output_dir instance.data["toBeRenderedOn"] = "deadline" @@ -247,17 +223,20 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "Vray Scene and Ass Scene options are mutually exclusive") if "vrayscene" in instance.data["families"]: + self.log.debug("Submitting V-Ray scene render..") vray_export_payload = self._get_vray_export_payload(payload_data) export_job = self.submit(vray_export_payload) payload = self._get_vray_render_payload(payload_data) elif "assscene" in instance.data["families"]: + self.log.debug("Submitting Arnold .ass standalone render..") ass_export_payload = self._get_arnold_export_payload(payload_data) export_job = self.submit(ass_export_payload) payload = self._get_arnold_render_payload(payload_data) else: + self.log.debug("Submitting MayaBatch render..") payload = self._get_maya_payload(payload_data) # Add export job as dependency -------------------------------------- @@ -274,6 +253,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.submit(self.assemble_payload(job_info, plugin_info)) def _tile_render(self, payload): + """Submit as tile render per frame with dependent assembly jobs.""" # As collected by super process() instance = self._instance @@ -315,7 +295,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): assembly_files = files # Define frame tile jobs - frame_jobs = {} + frame_file_hash = {} frame_payloads = {} file_index = 1 for file in files: @@ -343,9 +323,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.info("hashing {} - {}".format(file_index, file)) job_hash = hashlib.sha256( ("{}_{}".format(file_index, file)).encode("utf-8")) - frame_jobs[frame] = job_hash.hexdigest() - new_job_info.ExtraInfo[0] = job_hash.hexdigest() + file_hash = job_hash.hexdigest() + frame_file_hash[frame] = file_hash + + new_job_info.ExtraInfo[0] = file_hash new_job_info.ExtraInfo[1] = file frame_payloads[frame] = self.assemble_payload( @@ -391,10 +373,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): REPL_FRAME_NUMBER, "\\1{}\\3".format("#" * len(frame)), file) - hash = frame_jobs[frame] + file_hash = frame_file_hash[frame] tile_job_id = frame_tile_job_id[frame] - frame_assembly_job_info.ExtraInfo[0] = hash + frame_assembly_job_info.ExtraInfo[0] = file_hash frame_assembly_job_info.ExtraInfo[1] = file frame_assembly_job_info.JobDependency = tile_job_id @@ -483,11 +465,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if int(rman_version.split(".")[0]) > 22: renderer = "renderman22" - plugin_info = { + plugin_info = copy.deepcopy(self.plugin_info) + plugin_info.update({ # Output directory and filename "OutputFilePath": data["dirname"].replace("\\", "/"), "OutputFilePrefix": layer_prefix, - } + }) return job_info, plugin_info @@ -710,6 +693,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): end=int(self._instance.data["frameEndHandle"]), ) + @staticmethod + def _iter_expected_files(exp): + if isinstance(exp[0], dict): + for _aov, files in exp[0].items(): + for file in files: + yield file + else: + for file in exp: + yield file + def _format_tiles( filename, index, tiles_x, tiles_y, From 4cdd23021b4b251c13ae6139b06dac951d9ba6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Aug 2022 13:17:18 +0200 Subject: [PATCH 142/513] :bug: fix version resolution --- igniter/bootstrap_repos.py | 2 +- tools/create_zip.ps1 | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index c5003b062e..ccc9d4ac52 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -63,7 +63,7 @@ class OpenPypeVersion(semver.VersionInfo): """ staging = False path = None - _VERSION_REGEX = re.compile(r"(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$") # noqa: E501 + _VERSION_REGEX = re.compile(r"(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?") # noqa: E501 _installed_version = None def __init__(self, *args, **kwargs): diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1 index 7b852b7c54..e5ebc7678b 100644 --- a/tools/create_zip.ps1 +++ b/tools/create_zip.ps1 @@ -96,6 +96,7 @@ Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewl Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse| Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force -Recurse Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force +Write-Color -Text "OK" -Color Green Write-Color -Text ">>> ", "Generating zip from current sources ..." -Color Green, Gray $env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)" From 227b8405479e3a87b507e63a9b59fe473d7c9276 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:12:54 +0200 Subject: [PATCH 143/513] Refactor AbstractSubmitDeadline vars to allow easier access to indices --- .../deadline/abstract_submit_deadline.py | 251 +++++++----------- .../plugins/publish/submit_maya_deadline.py | 35 ++- 2 files changed, 115 insertions(+), 171 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index a3db3feac9..427faec115 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -9,6 +9,7 @@ import os from abc import abstractmethod import platform import getpass +from functools import partial from collections import OrderedDict import six @@ -66,6 +67,58 @@ def requests_get(*args, **kwargs): return requests.get(*args, **kwargs) +class DeadlineIndexedVar(dict): + """ + + Allows to set and query values by integer indices: + Query: var[1] or var.get(1) + Set: var[1] = "my_value" + Append: var += "value" + + Note: Iterating the instance is not guarantueed to be the order of the + indices. To do so iterate with `sorted()` + + """ + def __init__(self, key): + self.__key = key + + def next_available_index(self): + # Add as first unused entry + i = 0 + while i in self.keys(): + i += 1 + return i + + def serialize(self): + key = self.__key + + # Allow custom location for index in serialized string + if "{}" not in key: + key = key + "{}" + + return { + key.format(index): value for index, value in sorted(self.items()) + } + + def update(self, data): + # Force the integer key check + for key, value in data.items(): + self.__setitem__(key, value) + + def __iadd__(self, other): + index = self.next_available_index() + self[index] = other + return self + + def __setitem__(self, key, value): + if not isinstance(key, int): + raise TypeError("Key must be an integer: {}".format(key)) + + if key < 0: + raise ValueError("Negative index can't be set: {}".format(key)) + dict.__setitem__(self, key, value) + + @attr.s class DeadlineJobInfo(object): """Mapping of all Deadline *JobInfo* attributes. @@ -218,24 +271,8 @@ class DeadlineJobInfo(object): # Environment # ---------------------------------------------- - _environmentKeyValue = attr.ib(factory=list) - - @property - def EnvironmentKeyValue(self): # noqa: N802 - """Return all environment key values formatted for Deadline. - - Returns: - dict: as `{'EnvironmentKeyValue0', 'key=value'}` - - """ - out = {} - for index, v in enumerate(self._environmentKeyValue): - out["EnvironmentKeyValue{}".format(index)] = v - return out - - @EnvironmentKeyValue.setter - def EnvironmentKeyValue(self, val): # noqa: N802 - self._environmentKeyValue.append(val) + EnvironmentKeyValue = attr.ib(factory=partial(DeadlineIndexedVar, + "EnvironmentKeyValue")) IncludeEnvironment = attr.ib(default=None) # Default: false UseJobEnvironmentOnly = attr.ib(default=None) # Default: false @@ -243,142 +280,29 @@ class DeadlineJobInfo(object): # Job Extra Info # ---------------------------------------------- - _extraInfos = attr.ib(factory=list) - _extraInfoKeyValues = attr.ib(factory=list) - - @property - def ExtraInfo(self): # noqa: N802 - """Return all ExtraInfo values formatted for Deadline. - - Returns: - dict: as `{'ExtraInfo0': 'value'}` - - """ - out = {} - for index, v in enumerate(self._extraInfos): - out["ExtraInfo{}".format(index)] = v - return out - - @ExtraInfo.setter - def ExtraInfo(self, val): # noqa: N802 - self._extraInfos.append(val) - - @property - def ExtraInfoKeyValue(self): # noqa: N802 - """Return all ExtraInfoKeyValue values formatted for Deadline. - - Returns: - dict: as {'ExtraInfoKeyValue0': 'key=value'}` - - """ - out = {} - for index, v in enumerate(self._extraInfoKeyValues): - out["ExtraInfoKeyValue{}".format(index)] = v - return out - - @ExtraInfoKeyValue.setter - def ExtraInfoKeyValue(self, val): # noqa: N802 - self._extraInfoKeyValues.append(val) + ExtraInfo = attr.ib(factory=partial(DeadlineIndexedVar, "ExtraInfo")) + ExtraInfoKeyValue = attr.ib(factory=partial(DeadlineIndexedVar, + "ExtraInfoKeyValue")) # Task Extra Info Names # ---------------------------------------------- OverrideTaskExtraInfoNames = attr.ib(default=None) # Default: false - _taskExtraInfos = attr.ib(factory=list) - - @property - def TaskExtraInfoName(self): # noqa: N802 - """Return all TaskExtraInfoName values formatted for Deadline. - - Returns: - dict: as `{'TaskExtraInfoName0': 'value'}` - - """ - out = {} - for index, v in enumerate(self._taskExtraInfos): - out["TaskExtraInfoName{}".format(index)] = v - return out - - @TaskExtraInfoName.setter - def TaskExtraInfoName(self, val): # noqa: N802 - self._taskExtraInfos.append(val) + TaskExtraInfoName = attr.ib(factory=partial(DeadlineIndexedVar, + "TaskExtraInfoName")) # Output # ---------------------------------------------- - _outputFilename = attr.ib(factory=list) - _outputFilenameTile = attr.ib(factory=list) - _outputDirectory = attr.ib(factory=list) - - @property - def OutputFilename(self): # noqa: N802 - """Return all OutputFilename values formatted for Deadline. - - Returns: - dict: as `{'OutputFilename0': 'filename'}` - - """ - out = {} - for index, v in enumerate(self._outputFilename): - out["OutputFilename{}".format(index)] = v - return out - - @OutputFilename.setter - def OutputFilename(self, val): # noqa: N802 - self._outputFilename.append(val) - - @property - def OutputFilenameTile(self): # noqa: N802 - """Return all OutputFilename#Tile values formatted for Deadline. - - Returns: - dict: as `{'OutputFilenme#Tile': 'tile'}` - - """ - out = {} - for index, v in enumerate(self._outputFilenameTile): - out["OutputFilename{}Tile".format(index)] = v - return out - - @OutputFilenameTile.setter - def OutputFilenameTile(self, val): # noqa: N802 - self._outputFilenameTile.append(val) - - @property - def OutputDirectory(self): # noqa: N802 - """Return all OutputDirectory values formatted for Deadline. - - Returns: - dict: as `{'OutputDirectory0': 'dir'}` - - """ - out = {} - for index, v in enumerate(self._outputDirectory): - out["OutputDirectory{}".format(index)] = v - return out - - @OutputDirectory.setter - def OutputDirectory(self, val): # noqa: N802 - self._outputDirectory.append(val) + OutputFilename = attr.ib(factory=partial(DeadlineIndexedVar, + "OutputFilename")) + OutputFilenameTile = attr.ib(factory=partial(DeadlineIndexedVar, + "OutputFilename{}Tile")) + OutputDirectory = attr.ib(factory=partial(DeadlineIndexedVar, + "OutputDirectory")) # Asset Dependency # ---------------------------------------------- - _assetDependency = attr.ib(factory=list) - - @property - def AssetDependency(self): # noqa: N802 - """Return all OutputDirectory values formatted for Deadline. - - Returns: - dict: as `{'OutputDirectory0': 'dir'}` - - """ - out = {} - for index, v in enumerate(self._assetDependency): - out["AssetDependency{}".format(index)] = v - return out - - @OutputDirectory.setter - def AssetDependency(self, val): # noqa: N802 - self._assetDependency.append(val) + AssetDependency = attr.ib(factory=partial(DeadlineIndexedVar, + "AssetDependency")) # Tile Job # ---------------------------------------------- @@ -402,7 +326,7 @@ class DeadlineJobInfo(object): """ def filter_data(a, v): - if a.name.startswith("_"): + if isinstance(v, DeadlineIndexedVar): return False if v is None: return False @@ -410,16 +334,37 @@ class DeadlineJobInfo(object): serialized = attr.asdict( self, dict_factory=OrderedDict, filter=filter_data) - serialized.update(self.EnvironmentKeyValue) - serialized.update(self.ExtraInfo) - serialized.update(self.ExtraInfoKeyValue) - serialized.update(self.TaskExtraInfoName) - serialized.update(self.OutputFilename) - serialized.update(self.OutputFilenameTile) - serialized.update(self.OutputDirectory) - serialized.update(self.AssetDependency) + + # Custom serialize these attributes + for attribute in [ + self.EnvironmentKeyValue, + self.ExtraInfo, + self.ExtraInfoKeyValue, + self.TaskExtraInfoName, + self.OutputFilename, + self.OutputFilenameTile, + self.OutputDirectory, + self.AssetDependency + ]: + serialized.update(attribute.serialize()) + return serialized + def update(self, data): + """Update instance with data dict""" + for key, value in data.items(): + setattr(self, key, value) + + def __setattr__(self, key, value): + # Backwards compatibility: Allow appending to index vars by setting + # it on Job Info directly like: JobInfo.OutputFilename = filename + existing = getattr(self, key, None) + if isinstance(existing, DeadlineIndexedVar): + existing += value + return + + object.__setattr__(self, key, value) + @six.add_metaclass(AbstractMetaInstancePlugin) class AbstractSubmitDeadline(pyblish.api.InstancePlugin): diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 2afa1883c4..d979c92814 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -110,8 +110,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) - for key, value in render_globals.items(): - setattr(job_info, key, value) + job_info.update(render_globals) keys = [ "FTRACK_API_KEY", @@ -257,8 +256,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # As collected by super process() instance = self._instance - job_info = copy.deepcopy(self.job_info) - plugin_info = copy.deepcopy(self.plugin_info) + + payload_job_info, payload_plugin_info = payload + job_info = copy.deepcopy(payload_job_info) + plugin_info = copy.deepcopy(payload_plugin_info) # if we have sequence of files, we need to create tile job for # every frame @@ -309,16 +310,17 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): new_plugin_info = copy.deepcopy(plugin_info) # Add tile data into job info and plugin info - tiles_out, _ = _format_tiles( + tiles_data = _format_tiles( file, 0, instance.data.get("tilesX"), instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), - payload["PluginInfo"]["OutputFilePrefix"] - ) - new_job_info.update(tiles_out["JobInfo"]) - new_plugin_info.update(tiles_out["PluginInfo"]) + payload_plugin_info["OutputFilePrefix"] + )[0] + + new_job_info.update(tiles_data["JobInfo"]) + new_plugin_info.update(tiles_data["PluginInfo"]) self.log.info("hashing {} - {}".format(file_index, file)) job_hash = hashlib.sha256( @@ -342,15 +344,13 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Submit frame tile jobs frame_tile_job_id = {} for frame, tile_job_payload in frame_payloads.items(): - response = self.submit(tile_job_payload) - job_id = response.json()["_id"] + job_id = self.submit(tile_job_payload) frame_tile_job_id[frame] = job_id # Define assembly payloads assembly_job_info = copy.deepcopy(job_info) assembly_job_info.Plugin = self.tile_assembler_plugin - assembly_job_info.Name = "{job.Name} - Tile Assembly Job".format( - job=job_info) + assembly_job_info.Name += " - Tile Assembly Job" assembly_job_info.Frames = 1 assembly_job_info.MachineLimit = 1 assembly_job_info.Priority = instance.data.get("tile_priority", @@ -411,10 +411,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), - payload["PluginInfo"]["OutputFilePrefix"] + payload_plugin_info["OutputFilePrefix"] )[1] - sorted(tiles) - for k, v in tiles.items(): + for k, v in sorted(tiles.items()): print("{}={}".format(k, v), file=cf) payload = self.assemble_payload( @@ -431,8 +430,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.info("submitting assembly job {} of {}".format( i+1, len(assembly_payloads) )) - response = self.submit(payload) - assembly_job_ids.append(response.json()["_id"]) + assembly_job_id = self.submit(payload) + assembly_job_ids.append(assembly_job_id) instance.data["assemblySubmissionJobs"] = assembly_job_ids From a7293f2a4f6a30297eea15297f5b25100e11e9f1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:13:14 +0200 Subject: [PATCH 144/513] Fix indentation --- openpype/modules/deadline/abstract_submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 427faec115..e1bdcb10d9 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -295,7 +295,7 @@ class DeadlineJobInfo(object): OutputFilename = attr.ib(factory=partial(DeadlineIndexedVar, "OutputFilename")) OutputFilenameTile = attr.ib(factory=partial(DeadlineIndexedVar, - "OutputFilename{}Tile")) + "OutputFilename{}Tile")) OutputDirectory = attr.ib(factory=partial(DeadlineIndexedVar, "OutputDirectory")) From 91a3d8494bf8b65fe37560a02018edf59433caa6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:13:39 +0200 Subject: [PATCH 145/513] Disable aux files for now since it's not supported by Deadline Webservice --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 ++- 1 file changed, 2 insertions(+), 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 d979c92814..7694e80e9a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -419,8 +419,9 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): payload = self.assemble_payload( job_info=frame_assembly_job_info, plugin_info=assembly_plugin_info.copy(), + # todo: aux file transfers don't work with deadline webservice # add config file as job auxFile - aux_files=[config_file] + # aux_files=[config_file] ) assembly_payloads.append(payload) From 39d216797dacad00a3f42102e146c2806b9f8244 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:17:40 +0200 Subject: [PATCH 146/513] Force integer pixel values --- .../deadline/plugins/publish/submit_maya_deadline.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 7694e80e9a..3fbff0153b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -765,10 +765,10 @@ def _format_tiles( tiles_x, tiles_y ) - top = int(height) - (tile_y * h_space) - bottom = int(height) - ((tile_y - 1) * h_space) - 1 - left = (tile_x - 1) * w_space - right = (tile_x * w_space) - 1 + top = int(height - (tile_y * h_space)) + bottom = int(height - ((tile_y - 1) * h_space) - 1) + left = int((tile_x - 1) * w_space) + right = int((tile_x * w_space) - 1) # Job Info new_filename = "{}/{}{}".format( From a7190a51ad74823c069c73dbe8f7ec0f4c6daba6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:19:52 +0200 Subject: [PATCH 147/513] Force integer pixel values --- .../plugins/publish/submit_maya_deadline.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 7966861358..3ac9df07d6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -141,17 +141,21 @@ def _format_tiles( out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) - out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 - out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 - out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 - out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + top = int(height) - (tile_y * h_space) + bottom = int(height) - ((tile_y - 1) * h_space) - 1 + left = (tile_x - 1) * w_space + right = (tile_x * w_space) - 1 + + out["PluginInfo"]["RegionTop{}".format(tile)] = int(top) + out["PluginInfo"]["RegionBottom{}".format(tile)] = int(bottom) + out["PluginInfo"]["RegionLeft{}".format(tile)] = int(left) + out["PluginInfo"]["RegionRight{}".format(tile)] = int(right) cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename - cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space - - cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) + cfg["Tile{}X".format(tile)] = int(left) + cfg["Tile{}Y".format(tile)] = int(top) cfg["Tile{}Width".format(tile)] = w_space cfg["Tile{}Height".format(tile)] = h_space From 2fb7cabca49bae51162e2a82dfdc7d225094ed36 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:39:28 +0200 Subject: [PATCH 148/513] Shush hound --- .../deadline/abstract_submit_deadline.py | 4 ++-- .../plugins/publish/submit_maya_deadline.py | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index e1bdcb10d9..35b114da95 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -623,8 +623,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): for i in context: is_workfile = ( - "workfile" in i.data.get("families", []) or - i.data["family"] == "workfile" + "workfile" in i.data.get("families", []) or + i.data["family"] == "workfile" ) if not is_workfile: continue diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 3fbff0153b..1b69f8b4e9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -357,14 +357,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.tile_priority) assembly_plugin_info = { - "CleanupTiles": 1, - "ErrorOnMissing": True, - "Renderer": self._instance.data["renderer"] + "CleanupTiles": 1, + "ErrorOnMissing": True, + "Renderer": self._instance.data["renderer"] } assembly_payloads = [] output_dir = self.job_info.OutputDirectory[0] - for i, file in enumerate(assembly_files): + for file in assembly_files: frame = re.search(R_FRAME_NUMBER, file).group("frame") frame_assembly_job_info = copy.deepcopy(assembly_job_info) @@ -383,7 +383,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # write assembly job config files now = datetime.now() - config_file = os.path.join(output_dir, + config_file = os.path.join( + output_dir, "{}_config_{}.txt".format( os.path.splitext(file)[0], now.strftime("%Y_%m_%d_%H_%M_%S") @@ -427,10 +428,12 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Submit assembly jobs assembly_job_ids = [] + num_assemblies = len(assembly_payloads) for i, payload in enumerate(assembly_payloads): - self.log.info("submitting assembly job {} of {}".format( - i+1, len(assembly_payloads) - )) + self.log.info( + "submitting assembly job {} of {}".format(i + 1, + num_assemblies) + ) assembly_job_id = self.submit(payload) assembly_job_ids.append(assembly_job_id) @@ -682,7 +685,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): pf.writelines(scene_data) pf.truncate() self.log.info("Applied {} patch to scene.".format( - patches[i]["name"] + patches[i]["name"] )) def _job_info_label(self, label): From e9e01e3163079bc9f6c48fe633aed92592928328 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:43:18 +0200 Subject: [PATCH 149/513] Use update method --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 1b69f8b4e9..9692b136e9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -69,8 +69,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # todo: test whether this works for existing production cases # where custom jobInfo was stored in the project settings - for key, value in self.jobInfo.items(): - setattr(job_info, key, value) + job_info.update(self.jobInfo) instance = self._instance context = instance.context From f9c214e435a53ecbf8b5f0aba57292511a1e0873 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 17:02:23 +0200 Subject: [PATCH 150/513] Only apply `RenderSetupIncludeLights` when value is not None --- .../deadline/plugins/publish/submit_maya_deadline.py | 10 +++++++++- 1 file changed, 9 insertions(+), 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 3ac9df07d6..92c50c3e80 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -509,7 +509,15 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): self.payload_skeleton["JobInfo"]["Comment"] = comment self.payload_skeleton["PluginInfo"]["RenderLayer"] = renderlayer - self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = instance.data.get("renderSetupIncludeLights") # noqa + # Only set RenderSetupIncludeLights when not None + rs_include_lights = instance.data.get("renderSetupIncludeLights") + if rs_include_lights is not None: + self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = ( + rs_include_lights + ) + else: + self.payload_skeleton["PluginInfo"].pop("RenderSetupIncludeLights") + # Adding file dependencies. dependencies = instance.context.data["fileDependencies"] dependencies.append(filepath) From 37b2d85aa762ed4908fe29e3a57a11aef8e459cf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 17:02:55 +0200 Subject: [PATCH 151/513] Fix indentation (shush hound) --- .../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 92c50c3e80..0a18506bd4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -513,7 +513,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): rs_include_lights = instance.data.get("renderSetupIncludeLights") if rs_include_lights is not None: self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = ( - rs_include_lights + rs_include_lights ) else: self.payload_skeleton["PluginInfo"].pop("RenderSetupIncludeLights") From 1f8c7e8ea527adfd16bf209781e2997d03f3e189 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 20:48:23 +0200 Subject: [PATCH 152/513] Force integer division --- .../plugins/publish/submit_maya_deadline.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 0a18506bd4..ac9d5a3d79 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -117,8 +117,8 @@ def _format_tiles( tile = 0 out = {"JobInfo": {}, "PluginInfo": {}} cfg = OrderedDict() - w_space = width / tiles_x - h_space = height / tiles_y + w_space = width // tiles_x + h_space = height // tiles_y cfg["TilesCropped"] = "False" @@ -146,16 +146,16 @@ def _format_tiles( left = (tile_x - 1) * w_space right = (tile_x * w_space) - 1 - out["PluginInfo"]["RegionTop{}".format(tile)] = int(top) - out["PluginInfo"]["RegionBottom{}".format(tile)] = int(bottom) - out["PluginInfo"]["RegionLeft{}".format(tile)] = int(left) - out["PluginInfo"]["RegionRight{}".format(tile)] = int(right) + out["PluginInfo"]["RegionTop{}".format(tile)] = top + out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom + out["PluginInfo"]["RegionLeft{}".format(tile)] = left + out["PluginInfo"]["RegionRight{}".format(tile)] = right cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename - cfg["Tile{}X".format(tile)] = int(left) - cfg["Tile{}Y".format(tile)] = int(top) + cfg["Tile{}X".format(tile)] = left + cfg["Tile{}Y".format(tile)] = top cfg["Tile{}Width".format(tile)] = w_space cfg["Tile{}Height".format(tile)] = h_space From 67ee5b5710548a0d390d9aa34b13563da7bda30d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 31 Aug 2022 10:35:52 +0200 Subject: [PATCH 153/513] Format with signed numbers (include + or -) This allows negative offsets to be pasted --- .../plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 9fca1b5391..05899de5e1 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -453,7 +453,7 @@ class OpenPypeTileAssembler(DeadlinePlugin): # Swap to have input as foreground args.append("--swap") # Paste foreground to background - args.append("--paste +{}+{}".format(pos_x, pos_y)) + args.append("--paste {x:+d}{y:+d}".format(x=pos_x, y=pos_y)) args.append("-o") args.append(output_path) From 1da0f46930dacb69ff13cb0984ebfb2b341c6ceb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 31 Aug 2022 10:58:41 +0200 Subject: [PATCH 154/513] Fix docstring typos --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index ac9d5a3d79..e77c86ec43 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -103,13 +103,13 @@ def _format_tiles( filename (str): Filename to process as tiles. index (int): Index of that file if it is sequence. tiles_x (int): Number of tiles in X. - tiles_y (int): Number if tikes in Y. + tiles_y (int): Number of tiles in Y. width (int): Width resolution of final image. height (int): Height resolution of final image. prefix (str): Image prefix. Returns: - (dict, dict): Tuple of two dictionaires - first can be used to + (dict, dict): Tuple of two dictionaries - first can be used to extend JobInfo, second has tiles x, y, width and height used for assembler configuration. From acf3d67f242625f8a91317b602ffa6467ff7cc6b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 15:59:20 +0200 Subject: [PATCH 155/513] moved 'create_workdir_extra_folders' to 'openpype.pipeline.workfile' --- .../hooks/pre_create_extra_workdir_folders.py | 6 +- openpype/lib/path_tools.py | 89 ++++++++++++------- openpype/lib/plugin_tools.py | 1 - openpype/pipeline/workfile/__init__.py | 4 + openpype/pipeline/workfile/path_resolving.py | 57 ++++++++++++ openpype/tools/workfiles/files_widget.py | 10 +-- 6 files changed, 126 insertions(+), 41 deletions(-) diff --git a/openpype/hooks/pre_create_extra_workdir_folders.py b/openpype/hooks/pre_create_extra_workdir_folders.py index d79c5831ee..c5af620c87 100644 --- a/openpype/hooks/pre_create_extra_workdir_folders.py +++ b/openpype/hooks/pre_create_extra_workdir_folders.py @@ -1,8 +1,6 @@ import os -from openpype.lib import ( - PreLaunchHook, - create_workdir_extra_folders -) +from openpype.lib import PreLaunchHook +from openpype.pipeline.workfile import create_workdir_extra_folders class AddLastWorkfileToLaunchArgs(PreLaunchHook): diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index f60b2fa722..671591bca4 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -16,6 +16,51 @@ from .profiles_filtering import filter_profiles log = logging.getLogger(__name__) +class PathToolsDeprecatedWarning(DeprecationWarning): + pass + + +def deprecated(new_destination): + """Mark functions as deprecated. + + It will result in a warning being emitted when the function is used. + """ + + func = None + if callable(new_destination): + func = new_destination + new_destination = None + + def _decorator(decorated_func): + if new_destination is None: + warning_message = ( + " Please check content of deprecated function to figure out" + " possible replacement." + ) + else: + warning_message = " Please replace your usage with '{}'.".format( + new_destination + ) + + @functools.wraps(decorated_func) + def wrapper(*args, **kwargs): + warnings.simplefilter("always", PathToolsDeprecatedWarning) + warnings.warn( + ( + "Call to deprecated function '{}'" + "\nFunction was moved or removed.{}" + ).format(decorated_func.__name__, warning_message), + category=PathToolsDeprecatedWarning, + stacklevel=4 + ) + return decorated_func(*args, **kwargs) + return wrapper + + if func is None: + return _decorator + return _decorator(func) + + def format_file_size(file_size, suffix=None): """Returns formatted string with size in appropriate unit. @@ -333,6 +378,7 @@ def get_project_basic_paths(project_name): return _list_path_items(folder_structure) +@deprecated("openpype.pipeline.workfile.create_workdir_extra_folders") def create_workdir_extra_folders( workdir, host_name, task_type, task_name, project_name, project_settings=None @@ -349,37 +395,18 @@ def create_workdir_extra_folders( project_name (str): Name of project on which task is. project_settings (dict): Prepared project settings. Are loaded if not passed. + + Deprecated: + Function will be removed after release version 3.16.* """ - # Load project settings if not set - if not project_settings: - project_settings = get_project_settings(project_name) - # Load extra folders profiles - extra_folders_profiles = ( - project_settings["global"]["tools"]["Workfiles"]["extra_folders"] + from openpype.pipeline.project_folders import create_workdir_extra_folders + + return create_workdir_extra_folders( + workdir, + host_name, + task_type, + task_name, + project_name, + project_settings ) - # Skip if are empty - if not extra_folders_profiles: - return - - # Prepare profiles filters - filter_data = { - "task_types": task_type, - "task_names": task_name, - "hosts": host_name - } - profile = filter_profiles(extra_folders_profiles, filter_data) - if profile is None: - return - - for subfolder in profile["folders"]: - # Make sure backslashes are converted to forwards slashes - # and does not start with slash - subfolder = subfolder.replace("\\", "/").lstrip("/") - # Skip empty strings - if not subfolder: - continue - - fullpath = os.path.join(workdir, subfolder) - if not os.path.exists(fullpath): - os.makedirs(fullpath) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 81d268ea1c..1e157dfbfd 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -3,7 +3,6 @@ import os import logging import re -import json import warnings import functools diff --git a/openpype/pipeline/workfile/__init__.py b/openpype/pipeline/workfile/__init__.py index 0aad29b6f9..94ecc81bd6 100644 --- a/openpype/pipeline/workfile/__init__.py +++ b/openpype/pipeline/workfile/__init__.py @@ -9,6 +9,8 @@ from .path_resolving import ( get_custom_workfile_template, get_custom_workfile_template_by_string_context, + + create_workdir_extra_folders, ) from .build_workfile import BuildWorkfile @@ -26,5 +28,7 @@ __all__ = ( "get_custom_workfile_template", "get_custom_workfile_template_by_string_context", + "create_workdir_extra_folders", + "BuildWorkfile", ) diff --git a/openpype/pipeline/workfile/path_resolving.py b/openpype/pipeline/workfile/path_resolving.py index 6d9e72dbd2..1243e84148 100644 --- a/openpype/pipeline/workfile/path_resolving.py +++ b/openpype/pipeline/workfile/path_resolving.py @@ -467,3 +467,60 @@ def get_custom_workfile_template_by_string_context( return get_custom_workfile_template( project_doc, asset_doc, task_name, host_name, anatomy, project_settings ) + + +def create_workdir_extra_folders( + workdir, + host_name, + task_type, + task_name, + project_name, + project_settings=None +): + """Create extra folders in work directory based on context. + + Args: + workdir (str): Path to workdir where workfiles is stored. + host_name (str): Name of host implementation. + task_type (str): Type of task for which extra folders should be + created. + task_name (str): Name of task for which extra folders should be + created. + project_name (str): Name of project on which task is. + project_settings (dict): Prepared project settings. Are loaded if not + passed. + """ + + # Load project settings if not set + if not project_settings: + project_settings = get_project_settings(project_name) + + # Load extra folders profiles + extra_folders_profiles = ( + project_settings["global"]["tools"]["Workfiles"]["extra_folders"] + ) + # Skip if are empty + if not extra_folders_profiles: + return + + # Prepare profiles filters + filter_data = { + "task_types": task_type, + "task_names": task_name, + "hosts": host_name + } + profile = filter_profiles(extra_folders_profiles, filter_data) + if profile is None: + return + + for subfolder in profile["folders"]: + # Make sure backslashes are converted to forwards slashes + # and does not start with slash + subfolder = subfolder.replace("\\", "/").lstrip("/") + # Skip empty strings + if not subfolder: + continue + + fullpath = os.path.join(workdir, subfolder) + if not os.path.exists(fullpath): + os.makedirs(fullpath) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index a5d5b14bb6..b4f5e422bc 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -10,10 +10,7 @@ from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate -from openpype.lib import ( - emit_event, - create_workdir_extra_folders, -) +from openpype.lib import emit_event from openpype.pipeline import ( registered_host, legacy_io, @@ -23,7 +20,10 @@ from openpype.pipeline.context_tools import ( compute_session_changes, change_current_context ) -from openpype.pipeline.workfile import get_workfile_template_key +from openpype.pipeline.workfile import ( + get_workfile_template_key, + create_workdir_extra_folders, +) from .model import ( WorkAreaFilesModel, From d3a9610c5a92f17bd319843eab4a99f6d68acce4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 16:09:15 +0200 Subject: [PATCH 156/513] moved helper function to pipeline.project_folders --- openpype/lib/path_tools.py | 64 +++++++++++----------------- openpype/pipeline/project_folders.py | 49 +++++++++++++++++++++ 2 files changed, 73 insertions(+), 40 deletions(-) create mode 100644 openpype/pipeline/project_folders.py diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 671591bca4..736eb0effc 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -277,58 +277,42 @@ def get_last_version_from_path(path_dir, filter): return None +@deprecated("openpype.pipeline.project_folders.concatenate_splitted_paths") def concatenate_splitted_paths(split_paths, anatomy): - pattern_array = re.compile(r"\[.*\]") - output = [] - for path_items in split_paths: - clean_items = [] - if isinstance(path_items, str): - path_items = [path_items] + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - for path_item in path_items: - if not re.match(r"{.+}", path_item): - path_item = re.sub(pattern_array, "", path_item) - clean_items.append(path_item) + from openpype.pipeline.project_folders import concatenate_splitted_paths - # backward compatibility - if "__project_root__" in path_items: - for root, root_path in anatomy.roots.items(): - if not os.path.exists(str(root_path)): - log.debug("Root {} path path {} not exist on \ - computer!".format(root, root_path)) - continue - clean_items = ["{{root[{}]}}".format(root), - r"{project[name]}"] + clean_items[1:] - output.append(os.path.normpath(os.path.sep.join(clean_items))) - continue - - output.append(os.path.normpath(os.path.sep.join(clean_items))) - - return output + return concatenate_splitted_paths(split_paths, anatomy) +@deprecated def get_format_data(anatomy): - project_doc = get_project(anatomy.project_name, fields=["data.code"]) - project_code = project_doc["data"]["code"] + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - return { - "root": anatomy.roots, - "project": { - "name": anatomy.project_name, - "code": project_code - }, - } + from openpype.pipeline.template_data import get_project_template_data + + data = get_project_template_data(project_name=anatomy.project_name) + data["root"] = anatomy.roots + return data +@deprecated("openpype.pipeline.project_folders.fill_paths") def fill_paths(path_list, anatomy): - format_data = get_format_data(anatomy) - filled_paths = [] + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - for path in path_list: - new_path = path.format(**format_data) - filled_paths.append(new_path) + from openpype.pipeline.project_folders import fill_paths - return filled_paths + return fill_paths(path_list, anatomy) def create_project_folders(basic_paths, project_name): diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py new file mode 100644 index 0000000000..256c4e73d8 --- /dev/null +++ b/openpype/pipeline/project_folders.py @@ -0,0 +1,49 @@ +import os +import re + +from openpype.lib import Logger + +from .template_data import get_project_template_data + + +def concatenate_splitted_paths(split_paths, anatomy): + log = Logger.get_logger("concatenate_splitted_paths") + pattern_array = re.compile(r"\[.*\]") + output = [] + for path_items in split_paths: + clean_items = [] + if isinstance(path_items, str): + path_items = [path_items] + + for path_item in path_items: + if not re.match(r"{.+}", path_item): + path_item = re.sub(pattern_array, "", path_item) + clean_items.append(path_item) + + # backward compatibility + if "__project_root__" in path_items: + for root, root_path in anatomy.roots.items(): + if not os.path.exists(str(root_path)): + log.debug("Root {} path path {} not exist on \ + computer!".format(root, root_path)) + continue + clean_items = ["{{root[{}]}}".format(root), + r"{project[name]}"] + clean_items[1:] + output.append(os.path.normpath(os.path.sep.join(clean_items))) + continue + + output.append(os.path.normpath(os.path.sep.join(clean_items))) + + return output + + +def fill_paths(path_list, anatomy): + format_data = get_project_template_data(project_name=anatomy.project_name) + format_data["root"] = anatomy.roots + filled_paths = [] + + for path in path_list: + new_path = path.format(**format_data) + filled_paths.append(new_path) + + return filled_paths From c7ffda124e36813d00757d5096475bcb902c60f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 16:10:54 +0200 Subject: [PATCH 157/513] moved 'create_project_folders' and 'get_project_basic_paths' --- openpype/lib/path_tools.py | 65 ++++++------------- .../action_create_project_structure.py | 7 +- openpype/pipeline/project_folders.py | 56 ++++++++++++++++ .../project_manager/project_manager/window.py | 24 +++---- 4 files changed, 88 insertions(+), 64 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 736eb0effc..5800498b07 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -1,21 +1,17 @@ import os import re -import abc -import json import logging -import six import platform +import functools +import warnings import clique -from openpype.client import get_project -from openpype.settings import get_project_settings - -from .profiles_filtering import filter_profiles - log = logging.getLogger(__name__) + + class PathToolsDeprecatedWarning(DeprecationWarning): pass @@ -315,51 +311,28 @@ def fill_paths(path_list, anatomy): return fill_paths(path_list, anatomy) +@deprecated("openpype.pipeline.project_folders.create_project_folders") def create_project_folders(basic_paths, project_name): - from openpype.pipeline import Anatomy - anatomy = Anatomy(project_name) + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - concat_paths = concatenate_splitted_paths(basic_paths, anatomy) - filled_paths = fill_paths(concat_paths, anatomy) + from openpype.pipeline.project_folders import create_project_folders - # Create folders - for path in filled_paths: - if os.path.exists(path): - log.debug("Folder already exists: {}".format(path)) - else: - log.debug("Creating folder: {}".format(path)) - os.makedirs(path) - - -def _list_path_items(folder_structure): - output = [] - for key, value in folder_structure.items(): - if not value: - output.append(key) - else: - paths = _list_path_items(value) - for path in paths: - if not isinstance(path, (list, tuple)): - path = [path] - - item = [key] - item.extend(path) - output.append(item) - - return output + return create_project_folders(project_name, basic_paths) +@deprecated("openpype.pipeline.project_folders.get_project_basic_paths") def get_project_basic_paths(project_name): - project_settings = get_project_settings(project_name) - folder_structure = ( - project_settings["global"]["project_folder_structure"] - ) - if not folder_structure: - return [] + """ + Deprecated: + Function will be removed after release version 3.16.* + """ - if isinstance(folder_structure, str): - folder_structure = json.loads(folder_structure) - return _list_path_items(folder_structure) + from openpype.pipeline.project_folders import get_project_basic_paths + + return get_project_basic_paths(project_name) @deprecated("openpype.pipeline.workfile.create_workdir_extra_folders") diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py index df914de854..7c896570b1 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py @@ -1,7 +1,10 @@ import re +from openpype.pipeline.project_folders import ( + get_project_basic_paths, + create_project_folders, +) from openpype_modules.ftrack.lib import BaseAction, statics_icon -from openpype.api import get_project_basic_paths, create_project_folders class CreateProjectFolders(BaseAction): @@ -81,7 +84,7 @@ class CreateProjectFolders(BaseAction): } # Invoking OpenPype API to create the project folders - create_project_folders(basic_paths, project_name) + create_project_folders(project_name, basic_paths) self.create_ftrack_entities(basic_paths, project_entity) self.trigger_event( diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py index 256c4e73d8..811b9aa648 100644 --- a/openpype/pipeline/project_folders.py +++ b/openpype/pipeline/project_folders.py @@ -1,8 +1,13 @@ import os import re +import json +import six + +from openpype.settings import get_project_settings from openpype.lib import Logger +from .anatomy import Anatomy from .template_data import get_project_template_data @@ -47,3 +52,54 @@ def fill_paths(path_list, anatomy): filled_paths.append(new_path) return filled_paths + + +def create_project_folders(project_name, basic_paths=None): + log = Logger.get_logger("create_project_folders") + anatomy = Anatomy(project_name) + if basic_paths is None: + basic_paths = get_project_basic_paths(project_name) + + concat_paths = concatenate_splitted_paths(basic_paths, anatomy) + filled_paths = fill_paths(concat_paths, anatomy) + + # Create folders + for path in filled_paths: + if os.path.exists(path): + log.debug("Folder already exists: {}".format(path)) + else: + log.debug("Creating folder: {}".format(path)) + os.makedirs(path) + return filled_paths + + +def _list_path_items(folder_structure): + output = [] + for key, value in folder_structure.items(): + if not value: + output.append(key) + continue + + paths = _list_path_items(value) + for path in paths: + if not isinstance(path, (list, tuple)): + path = [path] + + item = [key] + item.extend(path) + output.append(item) + + return output + + +def get_project_basic_paths(project_name): + project_settings = get_project_settings(project_name) + folder_structure = ( + project_settings["global"]["project_folder_structure"] + ) + if not folder_structure: + return [] + + if isinstance(folder_structure, six.string_types): + folder_structure = json.loads(folder_structure) + return _list_path_items(folder_structure) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index c6ae0ff352..3b2dea8ca3 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -1,5 +1,12 @@ from Qt import QtWidgets, QtCore, QtGui +from openpype import resources +from openpype.style import load_stylesheet +from openpype.widgets import PasswordDialog +from openpype.lib import is_admin_password_required, Logger +from openpype.pipeline import AvalonMongoDB +from openpype.pipeline.project_folders import create_project_folders + from . import ( ProjectModel, ProjectProxyFilter, @@ -13,17 +20,6 @@ from . import ( ) from .widgets import ConfirmProjectDeletion from .style import ResourceCache -from openpype.style import load_stylesheet -from openpype.lib import is_admin_password_required -from openpype.widgets import PasswordDialog -from openpype.pipeline import AvalonMongoDB - -from openpype import resources -from openpype.api import ( - get_project_basic_paths, - create_project_folders, - Logger -) class ProjectManagerWindow(QtWidgets.QWidget): @@ -259,12 +255,8 @@ class ProjectManagerWindow(QtWidgets.QWidget): qm.Yes | qm.No) if ans == qm.Yes: try: - # Get paths based on presets - basic_paths = get_project_basic_paths(project_name) - if not basic_paths: - pass # Invoking OpenPype API to create the project folders - create_project_folders(basic_paths, project_name) + create_project_folders(project_name) except Exception as exc: self.log.warning( "Cannot create starting folders: {}".format(exc), From 27e74ee0095d33c22ee2059f12efb508662d066f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 16:15:28 +0200 Subject: [PATCH 158/513] skip folders creation if are not set --- openpype/pipeline/project_folders.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py index 811b9aa648..1bcba5c320 100644 --- a/openpype/pipeline/project_folders.py +++ b/openpype/pipeline/project_folders.py @@ -60,6 +60,9 @@ def create_project_folders(project_name, basic_paths=None): if basic_paths is None: basic_paths = get_project_basic_paths(project_name) + if not basic_paths: + return + concat_paths = concatenate_splitted_paths(basic_paths, anatomy) filled_paths = fill_paths(concat_paths, anatomy) @@ -70,7 +73,6 @@ def create_project_folders(project_name, basic_paths=None): else: log.debug("Creating folder: {}".format(path)) os.makedirs(path) - return filled_paths def _list_path_items(folder_structure): From 111a0be1a858f7568a169196c0be32e5d9da4a88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 16:21:23 +0200 Subject: [PATCH 159/513] removed not needed lines --- openpype/lib/path_tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/lib/path_tools.py b/openpype/lib/path_tools.py index 5800498b07..0b6d0a3391 100644 --- a/openpype/lib/path_tools.py +++ b/openpype/lib/path_tools.py @@ -10,8 +10,6 @@ import clique log = logging.getLogger(__name__) - - class PathToolsDeprecatedWarning(DeprecationWarning): pass From 137e4098897942a0baa15e01ab6d1950996900ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 18:08:16 +0200 Subject: [PATCH 160/513] convert functions to be usable in other files of client --- openpype/client/entities.py | 82 +++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 3d2730a17c..a4bd07838a 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -32,17 +32,37 @@ def _prepare_fields(fields, required_fields=None): return output -def _convert_id(in_id): +def convert_id(in_id): + """Helper function for conversion of id from string to ObjectId. + + Args: + in_id (Union[str, ObjectId, Any]): Entity id that should be converted + to right type for queries. + + Returns: + Union[ObjectId, Any]: Converted ids to ObjectId or in type. + """ + if isinstance(in_id, six.string_types): return ObjectId(in_id) return in_id -def _convert_ids(in_ids): +def convert_ids(in_ids): + """Helper function for conversion of ids from string to ObjectId. + + Args: + in_ids (Iterable[Union[str, ObjectId, Any]]): List of entity ids that + should be converted to right type for queries. + + Returns: + List[ObjectId]: Converted ids to ObjectId. + """ + _output = set() for in_id in in_ids: if in_id is not None: - _output.add(_convert_id(in_id)) + _output.add(convert_id(in_id)) return list(_output) @@ -115,7 +135,7 @@ def get_asset_by_id(project_name, asset_id, fields=None): None: Asset was not found by id. """ - asset_id = _convert_id(asset_id) + asset_id = convert_id(asset_id) if not asset_id: return None @@ -196,7 +216,7 @@ def _get_assets( query_filter = {"type": {"$in": asset_types}} if asset_ids is not None: - asset_ids = _convert_ids(asset_ids) + asset_ids = convert_ids(asset_ids) if not asset_ids: return [] query_filter["_id"] = {"$in": asset_ids} @@ -207,7 +227,7 @@ def _get_assets( query_filter["name"] = {"$in": list(asset_names)} if parent_ids is not None: - parent_ids = _convert_ids(parent_ids) + parent_ids = convert_ids(parent_ids) if not parent_ids: return [] query_filter["data.visualParent"] = {"$in": parent_ids} @@ -307,7 +327,7 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): "type": "subset" } if asset_ids is not None: - asset_ids = _convert_ids(asset_ids) + asset_ids = convert_ids(asset_ids) if not asset_ids: return [] subset_query["parent"] = {"$in": asset_ids} @@ -347,7 +367,7 @@ def get_subset_by_id(project_name, subset_id, fields=None): Dict: Subset document which can be reduced to specified 'fields'. """ - subset_id = _convert_id(subset_id) + subset_id = convert_id(subset_id) if not subset_id: return None @@ -374,7 +394,7 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): if not subset_name: return None - asset_id = _convert_id(asset_id) + asset_id = convert_id(asset_id) if not asset_id: return None @@ -428,13 +448,13 @@ def get_subsets( query_filter = {"type": {"$in": subset_types}} if asset_ids is not None: - asset_ids = _convert_ids(asset_ids) + asset_ids = convert_ids(asset_ids) if not asset_ids: return [] query_filter["parent"] = {"$in": asset_ids} if subset_ids is not None: - subset_ids = _convert_ids(subset_ids) + subset_ids = convert_ids(subset_ids) if not subset_ids: return [] query_filter["_id"] = {"$in": subset_ids} @@ -449,7 +469,7 @@ def get_subsets( for asset_id, names in names_by_asset_ids.items(): if asset_id and names: or_query.append({ - "parent": _convert_id(asset_id), + "parent": convert_id(asset_id), "name": {"$in": list(names)} }) if not or_query: @@ -510,7 +530,7 @@ def get_version_by_id(project_name, version_id, fields=None): Dict: Version document which can be reduced to specified 'fields'. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id: return None @@ -537,7 +557,7 @@ def get_version_by_name(project_name, version, subset_id, fields=None): Dict: Version document which can be reduced to specified 'fields'. """ - subset_id = _convert_id(subset_id) + subset_id = convert_id(subset_id) if not subset_id: return None @@ -567,7 +587,7 @@ def version_is_latest(project_name, version_id): bool: True if is latest version from subset else False. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id: return False version_doc = get_version_by_id( @@ -610,13 +630,13 @@ def _get_versions( query_filter = {"type": {"$in": version_types}} if subset_ids is not None: - subset_ids = _convert_ids(subset_ids) + subset_ids = convert_ids(subset_ids) if not subset_ids: return [] query_filter["parent"] = {"$in": subset_ids} if version_ids is not None: - version_ids = _convert_ids(version_ids) + version_ids = convert_ids(version_ids) if not version_ids: return [] query_filter["_id"] = {"$in": version_ids} @@ -690,7 +710,7 @@ def get_hero_version_by_subset_id(project_name, subset_id, fields=None): Dict: Hero version entity data. """ - subset_id = _convert_id(subset_id) + subset_id = convert_id(subset_id) if not subset_id: return None @@ -720,7 +740,7 @@ def get_hero_version_by_id(project_name, version_id, fields=None): Dict: Hero version entity data. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id: return None @@ -786,7 +806,7 @@ def get_output_link_versions(project_name, version_id, fields=None): links for passed version. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id: return [] @@ -812,7 +832,7 @@ def get_last_versions(project_name, subset_ids, fields=None): dict[ObjectId, int]: Key is subset id and value is last version name. """ - subset_ids = _convert_ids(subset_ids) + subset_ids = convert_ids(subset_ids) if not subset_ids: return {} @@ -898,7 +918,7 @@ def get_last_version_by_subset_id(project_name, subset_id, fields=None): Dict: Version document which can be reduced to specified 'fields'. """ - subset_id = _convert_id(subset_id) + subset_id = convert_id(subset_id) if not subset_id: return None @@ -971,7 +991,7 @@ def get_representation_by_id(project_name, representation_id, fields=None): "type": {"$in": repre_types} } if representation_id is not None: - query_filter["_id"] = _convert_id(representation_id) + query_filter["_id"] = convert_id(representation_id) conn = get_project_connection(project_name) @@ -996,7 +1016,7 @@ def get_representation_by_name( to specified 'fields'. """ - version_id = _convert_id(version_id) + version_id = convert_id(version_id) if not version_id or not representation_name: return None repre_types = ["representation", "archived_representations"] @@ -1089,7 +1109,7 @@ def _get_representations( query_filter = {"type": {"$in": repre_types}} if representation_ids is not None: - representation_ids = _convert_ids(representation_ids) + representation_ids = convert_ids(representation_ids) if not representation_ids: return default_output query_filter["_id"] = {"$in": representation_ids} @@ -1100,7 +1120,7 @@ def _get_representations( query_filter["name"] = {"$in": list(representation_names)} if version_ids is not None: - version_ids = _convert_ids(version_ids) + version_ids = convert_ids(version_ids) if not version_ids: return default_output query_filter["parent"] = {"$in": version_ids} @@ -1111,7 +1131,7 @@ def _get_representations( for version_id, names in names_by_version_ids.items(): if version_id and names: or_query.append({ - "parent": _convert_id(version_id), + "parent": convert_id(version_id), "name": {"$in": list(names)} }) if not or_query: @@ -1361,7 +1381,7 @@ def get_thumbnail_id_from_source(project_name, src_type, src_id): if not src_type or not src_id: return None - query_filter = {"_id": _convert_id(src_id)} + query_filter = {"_id": convert_id(src_id)} conn = get_project_connection(project_name) src_doc = conn.find_one(query_filter, {"data.thumbnail_id"}) @@ -1388,7 +1408,7 @@ def get_thumbnails(project_name, thumbnail_ids, fields=None): """ if thumbnail_ids: - thumbnail_ids = _convert_ids(thumbnail_ids) + thumbnail_ids = convert_ids(thumbnail_ids) if not thumbnail_ids: return [] @@ -1416,7 +1436,7 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): if not thumbnail_id: return None - query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} + query_filter = {"type": "thumbnail", "_id": convert_id(thumbnail_id)} conn = get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) @@ -1444,7 +1464,7 @@ def get_workfile_info( query_filter = { "type": "workfile", - "parent": _convert_id(asset_id), + "parent": convert_id(asset_id), "task_name": task_name, "filename": filename } From a0b33deda33c1777f9cd2dd62c6dac6a535167fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 18:18:33 +0200 Subject: [PATCH 161/513] added functions to get linked ids --- openpype/client/__init__.py | 11 ++ openpype/client/entity_links.py | 232 ++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 openpype/client/entity_links.py diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 64a82334d9..d080425e3c 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -45,6 +45,13 @@ from .entities import ( get_workfile_info, ) +from .entity_links import ( + get_linked_asset_ids, + get_linked_assets, + get_linked_representation_ids, +) + + __all__ = ( "OpenPypeMongoConnection", @@ -88,4 +95,8 @@ __all__ = ( "get_thumbnail_id_from_source", "get_workfile_info", + + "get_linked_asset_ids", + "get_linked_assets", + "get_linked_representation_ids", ) diff --git a/openpype/client/entity_links.py b/openpype/client/entity_links.py new file mode 100644 index 0000000000..66214f469c --- /dev/null +++ b/openpype/client/entity_links.py @@ -0,0 +1,232 @@ +from .mongo import get_project_connection +from .entities import ( + get_assets, + get_asset_by_id, + get_representation_by_id, + convert_id, +) + + +def get_linked_asset_ids(project_name, asset_doc=None, asset_id=None): + """Extract linked asset ids from asset document. + + One of asset document or asset id must be passed. + + Note: + Asset links now works only from asset to assets. + + Args: + asset_doc (dict): Asset document from DB. + + Returns: + List[Union[ObjectId, str]]: Asset ids of input links. + """ + + output = [] + if not asset_doc and not asset_id: + return output + + if not asset_doc: + asset_doc = get_asset_by_id( + project_name, asset_id, fields=["data.inputLinks"] + ) + + input_links = asset_doc["data"].get("inputLinks") + if not input_links: + return output + + for item in input_links: + # Backwards compatibility for "_id" key which was replaced with + # "id" + if "_id" in item: + link_id = item["_id"] + else: + link_id = item["id"] + output.append(link_id) + return output + + +def get_linked_assets( + project_name, asset_doc=None, asset_id=None, fields=None +): + """Return linked assets based on passed asset document. + + One of asset document or asset id must be passed. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_doc (Dict[str, Any]): Asset document from database. + asset_id (Union[ObjectId, str]): Asset id. Can be used instead of + asset document. + fields (Iterable[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + List[Dict[str, Any]]: Asset documents of input links for passed + asset doc. + """ + + if not asset_doc: + if not asset_id: + return [] + asset_doc = get_asset_by_id( + project_name, + asset_id, + fields=["data.inputLinks"] + ) + if not asset_doc: + return [] + + link_ids = get_linked_asset_ids(project_name, asset_doc=asset_doc) + if not link_ids: + return [] + + return list(get_assets(project_name, asset_ids=link_ids, fields=fields)) + + +def get_linked_representation_id( + project_name, repre_doc=None, repre_id=None, link_type=None, max_depth=None +): + """Returns list of linked ids of particular type (if provided). + + One of representation document or representation id must be passed. + Note: + Representation links now works only from representation through version + back to representations. + + Args: + project_name (str): Name of project where look for links. + repre_doc (Dict[str, Any]): Representation document. + repre_id (Union[ObjectId, str]): Representation id. + link_type (str): Type of link (e.g. 'reference', ...). + max_depth (int): Limit recursion level. Default: 0 + + Returns: + List[ObjectId] Linked representation ids. + """ + + if repre_doc: + repre_id = repre_doc["_id"] + + if repre_id: + repre_id = convert_id(repre_id) + + if not repre_id and not repre_doc: + return [] + + version_id = None + if repre_doc: + version_id = repre_doc.get("parent") + + if not version_id: + repre_doc = get_representation_by_id( + project_name, repre_id, fields=["parent"] + ) + version_id = repre_doc["parent"] + + if not version_id: + return [] + + if max_depth is None: + max_depth = 0 + + match = { + "_id": version_id, + "type": {"$in": ["version", "hero_version"]} + } + + graph_lookup = { + "from": project_name, + "startWith": "$data.inputLinks.id", + "connectFromField": "data.inputLinks.id", + "connectToField": "_id", + "as": "outputs_recursive", + "depthField": "depth" + } + if max_depth != 0: + # We offset by -1 since 0 basically means no recursion + # but the recursion only happens after the initial lookup + # for outputs. + graph_lookup["maxDepth"] = max_depth - 1 + + query_pipeline = [ + # Match + {"$match": match}, + # Recursive graph lookup for inputs + {"$graphLookup": graph_lookup} + ] + + conn = get_project_connection(project_name) + result = conn.aggregate(query_pipeline) + referenced_version_ids = _process_referenced_pipeline_result( + result, link_type + ) + if not referenced_version_ids: + return [] + + ref_ids = conn.distinct( + "_id", + filter={ + "parent": {"$in": list(referenced_version_ids)}, + "type": "representation" + } + ) + + return list(ref_ids) + + +def _process_referenced_pipeline_result(result, link_type): + """Filters result from pipeline for particular link_type. + + Pipeline cannot use link_type directly in a query. + + Returns: + (list) + """ + + referenced_version_ids = set() + correctly_linked_ids = set() + for item in result: + input_links = item["data"].get("inputLinks") + if not input_links: + continue + + _filter_input_links( + input_links, + link_type, + correctly_linked_ids + ) + + # outputs_recursive in random order, sort by depth + outputs_recursive = item.get("outputs_recursive") + if not outputs_recursive: + continue + + for output in sorted(outputs_recursive, key=lambda o: o["depth"]): + output_links = output["data"].get("inputLinks") + if not output_links: + continue + + # Leaf + if output["_id"] not in correctly_linked_ids: + continue + + _filter_input_links( + output_links, + link_type, + correctly_linked_ids + ) + + referenced_version_ids.add(output["_id"]) + + return referenced_version_ids + + +def _filter_input_links(input_links, link_type, correctly_linked_ids): + for input_link in input_links: + if link_type and input_link["type"] != link_type: + continue + + link_id = input_link.get("id") or input_link.get("_id") + if link_id is not None: + correctly_linked_ids.add(link_id) From 17cfd18d1aa16720292b8f953db77039776ec47d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 18:19:51 +0200 Subject: [PATCH 162/513] use new location of 'get_linked_assets' --- openpype/lib/avalon_context.py | 36 +++++++++---------- .../workfile/abstract_template_loader.py | 6 ++-- openpype/pipeline/workfile/build_workfile.py | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 7d56d039d4..140ea887ff 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -236,6 +236,7 @@ def get_system_general_anatomy_data(system_settings=None): return get_general_template_data(system_settings) +@deprecated("openpype.client.get_linked_asset_ids") def get_linked_asset_ids(asset_doc): """Return linked asset ids for `asset_doc` from DB @@ -244,26 +245,20 @@ def get_linked_asset_ids(asset_doc): Returns: (list): MongoDB ids of input links. + + Deprecated: + Function will be removed after release version 3.16.* """ - output = [] - if not asset_doc: - return output - input_links = asset_doc["data"].get("inputLinks") or [] - if input_links: - for item in input_links: - # Backwards compatibility for "_id" key which was replaced with - # "id" - if "_id" in item: - link_id = item["_id"] - else: - link_id = item["id"] - output.append(link_id) + from openpype.client import get_linked_asset_ids + from openpype.pipeline import legacy_io - return output + project_name = legacy_io.active_project() + + return get_linked_asset_ids(project_name, asset_doc=asset_doc) -@with_pipeline_io +@deprecated("openpype.client.get_linked_assets") def get_linked_assets(asset_doc): """Return linked assets for `asset_doc` from DB @@ -272,14 +267,17 @@ def get_linked_assets(asset_doc): Returns: (list) Asset documents of input links for passed asset doc. + + Deprecated: + Function will be removed after release version 3.15.* """ - link_ids = get_linked_asset_ids(asset_doc) - if not link_ids: - return [] + from openpype.pipeline import legacy_io + from openpype.client import get_linked_assets project_name = legacy_io.active_project() - return list(get_assets(project_name, link_ids)) + + return get_linked_assets(project_name, asset_doc=asset_doc) @deprecated("openpype.client.get_last_version_by_subset_name") diff --git a/openpype/pipeline/workfile/abstract_template_loader.py b/openpype/pipeline/workfile/abstract_template_loader.py index 05a98a1ddc..82a0fd33e9 100644 --- a/openpype/pipeline/workfile/abstract_template_loader.py +++ b/openpype/pipeline/workfile/abstract_template_loader.py @@ -5,13 +5,15 @@ import six import logging from functools import reduce -from openpype.client import get_asset_by_name +from openpype.client import ( + get_asset_by_name, + get_linked_assets, +) from openpype.settings import get_project_settings from openpype.lib import ( StringTemplate, Logger, filter_profiles, - get_linked_assets, ) from openpype.pipeline import legacy_io, Anatomy from openpype.pipeline.load import ( diff --git a/openpype/pipeline/workfile/build_workfile.py b/openpype/pipeline/workfile/build_workfile.py index bb6fcb4189..0b8a444436 100644 --- a/openpype/pipeline/workfile/build_workfile.py +++ b/openpype/pipeline/workfile/build_workfile.py @@ -8,10 +8,10 @@ from openpype.client import ( get_subsets, get_last_versions, get_representations, + get_linked_assets, ) from openpype.settings import get_project_settings from openpype.lib import ( - get_linked_assets, filter_profiles, Logger, ) From c8ef54e9ce840c7ad09c3ca2f52d28a8c9f1c622 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 31 Aug 2022 18:22:04 +0200 Subject: [PATCH 163/513] use new source of 'get_linked_ids_for_representations' --- openpype/lib/avalon_context.py | 116 +++++------------------------- openpype/plugins/load/add_site.py | 10 +-- 2 files changed, 25 insertions(+), 101 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 140ea887ff..1fff45f262 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1083,9 +1083,10 @@ def get_last_workfile( ) -@with_pipeline_io -def get_linked_ids_for_representations(project_name, repre_ids, dbcon=None, - link_type=None, max_depth=0): +@deprecated("openpype.client.get_linked_ids_for_representations") +def get_linked_ids_for_representations( + project_name, repre_ids, dbcon=None, link_type=None, max_depth=0 +): """Returns list of linked ids of particular type (if provided). Goes from representations to version, back to representations @@ -1096,104 +1097,25 @@ def get_linked_ids_for_representations(project_name, repre_ids, dbcon=None, with Session. link_type (str): ['reference', '..] max_depth (int): limit how many levels of recursion + Returns: (list) of ObjectId - linked representations + + Deprecated: + Function will be removed after release version 3.16.* """ - # Create new dbcon if not passed and use passed project name - if not dbcon: - from openpype.pipeline import AvalonMongoDB - dbcon = AvalonMongoDB() - dbcon.Session["AVALON_PROJECT"] = project_name - # Validate that passed dbcon has same project - elif dbcon.Session["AVALON_PROJECT"] != project_name: - raise ValueError("Passed connection does not have right project") + + from openpype.client import get_linked_representation_ids if not isinstance(repre_ids, list): repre_ids = [repre_ids] - version_ids = dbcon.distinct("parent", { - "_id": {"$in": repre_ids}, - "type": "representation" - }) - - match = { - "_id": {"$in": version_ids}, - "type": "version" - } - - graph_lookup = { - "from": project_name, - "startWith": "$data.inputLinks.id", - "connectFromField": "data.inputLinks.id", - "connectToField": "_id", - "as": "outputs_recursive", - "depthField": "depth" - } - if max_depth != 0: - # We offset by -1 since 0 basically means no recursion - # but the recursion only happens after the initial lookup - # for outputs. - graph_lookup["maxDepth"] = max_depth - 1 - - pipeline_ = [ - # Match - {"$match": match}, - # Recursive graph lookup for inputs - {"$graphLookup": graph_lookup} - ] - - result = dbcon.aggregate(pipeline_) - referenced_version_ids = _process_referenced_pipeline_result(result, - link_type) - - ref_ids = dbcon.distinct( - "_id", - filter={ - "parent": {"$in": list(referenced_version_ids)}, - "type": "representation" - } - ) - - return list(ref_ids) - - -def _process_referenced_pipeline_result(result, link_type): - """Filters result from pipeline for particular link_type. - - Pipeline cannot use link_type directly in a query. - Returns: - (list) - """ - referenced_version_ids = set() - correctly_linked_ids = set() - for item in result: - input_links = item["data"].get("inputLinks", []) - correctly_linked_ids = _filter_input_links(input_links, - link_type, - correctly_linked_ids) - - # outputs_recursive in random order, sort by depth - outputs_recursive = sorted(item.get("outputs_recursive", []), - key=lambda d: d["depth"]) - - for output in outputs_recursive: - if output["_id"] not in correctly_linked_ids: # leaf - continue - - correctly_linked_ids = _filter_input_links( - output["data"].get("inputLinks", []), - link_type, - correctly_linked_ids) - - referenced_version_ids.add(output["_id"]) - - return referenced_version_ids - - -def _filter_input_links(input_links, link_type, correctly_linked_ids): - for input_link in input_links: - if not link_type or input_link["type"] == link_type: - correctly_linked_ids.add(input_link.get("id") or - input_link.get("_id")) # legacy - - return correctly_linked_ids + output = [] + for repre_id in repre_ids: + output.extend(get_linked_representation_ids( + project_name, + repre_id=repre_id, + link_type=link_type, + max_depth=max_depth + )) + return output diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 55fda55d17..388a871e9d 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -1,6 +1,6 @@ +from openpype.client import get_linked_ids_for_representations from openpype.modules import ModulesManager from openpype.pipeline import load -from openpype.lib.avalon_context import get_linked_ids_for_representations from openpype.modules.sync_server.utils import SiteAlreadyPresentError @@ -45,9 +45,11 @@ class AddSyncSite(load.LoaderPlugin): force=True) if family == "workfile": - links = get_linked_ids_for_representations(project_name, - [repre_id], - link_type="reference") + links = get_linked_ids_for_representations( + project_name, + repre_id=repre_id, + link_type="reference" + ) for link_repre_id in links: try: self.sync_server.add_site(project_name, link_repre_id, From e5c662d941aa1e09716de2b1ce8bdaf56f8d0ea5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 11:59:48 +0200 Subject: [PATCH 164/513] use Extractor from openpype.pipeline --- .../plugins/publish/extract_local_render.py | 14 +++++++++----- .../plugins/publish/extract_save_scene.py | 4 ++-- .../plugins/publish/remove_publish_highlight.py | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index 7323a0b125..dc65cee61d 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -2,14 +2,18 @@ import os import sys import six -import openpype.api +from openpype.lib import ( + get_ffmpeg_tool_path, + run_subprocess, +) +from openpype.pipeline import publish from openpype.hosts.aftereffects.api import get_stub -class ExtractLocalRender(openpype.api.Extractor): +class ExtractLocalRender(publish.Extractor): """Render RenderQueue locally.""" - order = openpype.api.Extractor.order - 0.47 + order = publish.Extractor.order - 0.47 label = "Extract Local Render" hosts = ["aftereffects"] families = ["renderLocal", "render.local"] @@ -53,7 +57,7 @@ class ExtractLocalRender(openpype.api.Extractor): instance.data["representations"] = [repre_data] - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") # Generate thumbnail. thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg") @@ -66,7 +70,7 @@ class ExtractLocalRender(openpype.api.Extractor): ] self.log.debug("Thumbnail args:: {}".format(args)) try: - output = openpype.lib.run_subprocess(args) + output = run_subprocess(args) except TypeError: self.log.warning("Error in creating thumbnail") six.reraise(*sys.exc_info()) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py b/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py index eb2977309f..343838eb49 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py @@ -1,13 +1,13 @@ import pyblish.api -import openpype.api +from openpype.pipeline import publish from openpype.hosts.aftereffects.api import get_stub class ExtractSaveScene(pyblish.api.ContextPlugin): """Save scene before extraction.""" - order = openpype.api.Extractor.order - 0.48 + order = publish.Extractor.order - 0.48 label = "Extract Save Scene" hosts = ["aftereffects"] diff --git a/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py b/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py index 5f3fcc3089..370f916f04 100644 --- a/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py +++ b/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py @@ -1,8 +1,8 @@ -import openpype.api +from openpype.pipeline import publish from openpype.hosts.aftereffects.api import get_stub -class RemovePublishHighlight(openpype.api.Extractor): +class RemovePublishHighlight(publish.Extractor): """Clean utf characters which are not working in DL Published compositions are marked with unicode icon which causes @@ -10,7 +10,7 @@ class RemovePublishHighlight(openpype.api.Extractor): rendering, add it later back to avoid confusion. """ - order = openpype.api.Extractor.order - 0.49 # just before save + order = publish.Extractor.order - 0.49 # just before save label = "Clean render comp" hosts = ["aftereffects"] families = ["render.farm"] From cb8cccc9e8fdeba54d2d32b1bbf947ac40a4b92a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:01:36 +0200 Subject: [PATCH 165/513] Use new import source of Extractor --- openpype/hosts/blender/plugins/publish/extract_abc.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_blend.py | 4 ++-- .../hosts/blender/plugins/publish/extract_blend_animation.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_camera.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_fbx.py | 4 ++-- .../hosts/blender/plugins/publish/extract_fbx_animation.py | 4 ++-- openpype/hosts/blender/plugins/publish/extract_layout.py | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py index a26a92f7e4..1cab9d225b 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -2,12 +2,12 @@ import os import bpy -from openpype import api +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractABC(api.Extractor): +class ExtractABC(publish.Extractor): """Extract as ABC.""" label = "Extract ABC" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py index 9add633f05..6a001b6f65 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -2,10 +2,10 @@ import os import bpy -import openpype.api +from openpype.pipeline import publish -class ExtractBlend(openpype.api.Extractor): +class ExtractBlend(publish.Extractor): """Extract a blend file.""" label = "Extract Blend" diff --git a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py index 4917223331..477411b73d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_blend_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend_animation.py @@ -2,10 +2,10 @@ import os import bpy -import openpype.api +from openpype.pipeline import publish -class ExtractBlendAnimation(openpype.api.Extractor): +class ExtractBlendAnimation(publish.Extractor): """Extract a blend file.""" label = "Extract Blend" diff --git a/openpype/hosts/blender/plugins/publish/extract_camera.py b/openpype/hosts/blender/plugins/publish/extract_camera.py index b2c7611b58..9fd181825c 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera.py @@ -2,11 +2,11 @@ import os import bpy -from openpype import api +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin -class ExtractCamera(api.Extractor): +class ExtractCamera(publish.Extractor): """Extract as the camera as FBX.""" label = "Extract Camera" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 3ac66f33a4..0ad797c226 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -2,12 +2,12 @@ import os import bpy -from openpype import api +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractFBX(api.Extractor): +class ExtractFBX(publish.Extractor): """Extract as FBX.""" label = "Extract FBX" diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 4b4a92932a..062b42e99d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -5,12 +5,12 @@ import bpy import bpy_extras import bpy_extras.anim_utils -from openpype import api +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -class ExtractAnimationFBX(api.Extractor): +class ExtractAnimationFBX(publish.Extractor): """Extract as animation.""" label = "Extract FBX" diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 8502c6fbd4..f2d04f1178 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -6,12 +6,12 @@ import bpy_extras import bpy_extras.anim_utils from openpype.client import get_representation_by_name +from openpype.pipeline import publish from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY -import openpype.api -class ExtractLayout(openpype.api.Extractor): +class ExtractLayout(publish.Extractor): """Extract a layout.""" label = "Extract Layout" From 47908465197226814aee76e51e74de7fedc8b01a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:03:27 +0200 Subject: [PATCH 166/513] Use new import source of Extractor --- openpype/hosts/harmony/plugins/publish/extract_palette.py | 4 ++-- openpype/hosts/harmony/plugins/publish/extract_template.py | 7 +++---- openpype/hosts/harmony/plugins/publish/extract_workfile.py | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/harmony/plugins/publish/extract_palette.py b/openpype/hosts/harmony/plugins/publish/extract_palette.py index fae778f6b0..69c6e098ff 100644 --- a/openpype/hosts/harmony/plugins/publish/extract_palette.py +++ b/openpype/hosts/harmony/plugins/publish/extract_palette.py @@ -6,10 +6,10 @@ import csv from PIL import Image, ImageDraw, ImageFont import openpype.hosts.harmony.api as harmony -import openpype.api +from openpype.pipeline import publish -class ExtractPalette(openpype.api.Extractor): +class ExtractPalette(publish.Extractor): """Extract palette.""" label = "Extract Palette" diff --git a/openpype/hosts/harmony/plugins/publish/extract_template.py b/openpype/hosts/harmony/plugins/publish/extract_template.py index d25b07bba3..458bf25a3c 100644 --- a/openpype/hosts/harmony/plugins/publish/extract_template.py +++ b/openpype/hosts/harmony/plugins/publish/extract_template.py @@ -3,12 +3,11 @@ import os import shutil -import openpype.api +from openpype.pipeline import publish import openpype.hosts.harmony.api as harmony -import openpype.hosts.harmony -class ExtractTemplate(openpype.api.Extractor): +class ExtractTemplate(publish.Extractor): """Extract the connected nodes to the composite instance.""" label = "Extract Template" @@ -50,7 +49,7 @@ class ExtractTemplate(openpype.api.Extractor): dependencies.remove(instance.data["setMembers"][0]) # Export template. - openpype.hosts.harmony.api.export_template( + harmony.export_template( unique_backdrops, dependencies, filepath ) diff --git a/openpype/hosts/harmony/plugins/publish/extract_workfile.py b/openpype/hosts/harmony/plugins/publish/extract_workfile.py index 7f25ec8150..9bb3090558 100644 --- a/openpype/hosts/harmony/plugins/publish/extract_workfile.py +++ b/openpype/hosts/harmony/plugins/publish/extract_workfile.py @@ -4,10 +4,10 @@ import os import shutil from zipfile import ZipFile -import openpype.api +from openpype.pipeline import publish -class ExtractWorkfile(openpype.api.Extractor): +class ExtractWorkfile(publish.Extractor): """Extract and zip complete workfile folder into zip.""" label = "Extract Workfile" From b939556394fa1456355fe952493cbc13e1a2735d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:04:40 +0200 Subject: [PATCH 167/513] Use new import source of Extractor --- .../hiero/plugins/publish/extract_clip_effects.py | 5 +++-- .../hosts/hiero/plugins/publish/extract_frames.py | 13 +++++++++---- .../hiero/plugins/publish/extract_thumbnail.py | 5 +++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py b/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py index 5b0aa270a7..7fb381ff7e 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py @@ -2,10 +2,11 @@ import os import json import pyblish.api -import openpype + +from openpype.pipeline import publish -class ExtractClipEffects(openpype.api.Extractor): +class ExtractClipEffects(publish.Extractor): """Extract clip effects instances.""" order = pyblish.api.ExtractorOrder diff --git a/openpype/hosts/hiero/plugins/publish/extract_frames.py b/openpype/hosts/hiero/plugins/publish/extract_frames.py index aa3eda2e9f..f865d2fb39 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_frames.py +++ b/openpype/hosts/hiero/plugins/publish/extract_frames.py @@ -1,9 +1,14 @@ import os import pyblish.api -import openpype + +from openpype.lib import ( + get_oiio_tools_path, + run_subprocess, +) +from openpype.pipeline import publish -class ExtractFrames(openpype.api.Extractor): +class ExtractFrames(publish.Extractor): """Extracts frames""" order = pyblish.api.ExtractorOrder @@ -13,7 +18,7 @@ class ExtractFrames(openpype.api.Extractor): movie_extensions = ["mov", "mp4"] def process(self, instance): - oiio_tool_path = openpype.lib.get_oiio_tools_path() + oiio_tool_path = get_oiio_tools_path() staging_dir = self.staging_dir(instance) output_template = os.path.join(staging_dir, instance.data["name"]) sequence = instance.context.data["activeTimeline"] @@ -43,7 +48,7 @@ class ExtractFrames(openpype.api.Extractor): args.extend(["--powc", "0.45,0.45,0.45,1.0"]) args.extend([input_path, "-o", output_path]) - output = openpype.api.run_subprocess(args) + output = run_subprocess(args) failed_output = "oiiotool produced no output." if failed_output in output: diff --git a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py index d12e7665bf..e64aa89b26 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py @@ -1,9 +1,10 @@ import os import pyblish.api -import openpype.api + +from openpype.pipeline import publish -class ExtractThumnail(openpype.api.Extractor): +class ExtractThumnail(publish.Extractor): """ Extractor for track item's tumnails """ From 2fbfe59d966a4a22dc3534be3f5bb97da08a65c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:09:18 +0200 Subject: [PATCH 168/513] Use new import source of Extractor --- openpype/hosts/houdini/plugins/publish/extract_alembic.py | 5 +++-- openpype/hosts/houdini/plugins/publish/extract_ass.py | 5 +++-- openpype/hosts/houdini/plugins/publish/extract_composite.py | 4 ++-- openpype/hosts/houdini/plugins/publish/extract_hda.py | 5 +++-- .../hosts/houdini/plugins/publish/extract_redshift_proxy.py | 5 +++-- openpype/hosts/houdini/plugins/publish/extract_usd.py | 5 +++-- .../hosts/houdini/plugins/publish/extract_usd_layered.py | 4 ++-- openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py | 5 +++-- 8 files changed, 22 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_alembic.py b/openpype/hosts/houdini/plugins/publish/extract_alembic.py index 83b790407f..758d4c560b 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_alembic.py +++ b/openpype/hosts/houdini/plugins/publish/extract_alembic.py @@ -1,11 +1,12 @@ import os import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.houdini.api.lib import render_rop -class ExtractAlembic(openpype.api.Extractor): +class ExtractAlembic(publish.Extractor): order = pyblish.api.ExtractorOrder label = "Extract Alembic" diff --git a/openpype/hosts/houdini/plugins/publish/extract_ass.py b/openpype/hosts/houdini/plugins/publish/extract_ass.py index e56e40df85..a302b451cb 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_ass.py +++ b/openpype/hosts/houdini/plugins/publish/extract_ass.py @@ -1,11 +1,12 @@ import os import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.houdini.api.lib import render_rop -class ExtractAss(openpype.api.Extractor): +class ExtractAss(publish.Extractor): order = pyblish.api.ExtractorOrder + 0.1 label = "Extract Ass" diff --git a/openpype/hosts/houdini/plugins/publish/extract_composite.py b/openpype/hosts/houdini/plugins/publish/extract_composite.py index f300b6d28d..23e875f107 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_composite.py +++ b/openpype/hosts/houdini/plugins/publish/extract_composite.py @@ -1,12 +1,12 @@ import os import pyblish.api -import openpype.api +from openpype.pipeline import publish from openpype.hosts.houdini.api.lib import render_rop -class ExtractComposite(openpype.api.Extractor): +class ExtractComposite(publish.Extractor): order = pyblish.api.ExtractorOrder label = "Extract Composite (Image Sequence)" diff --git a/openpype/hosts/houdini/plugins/publish/extract_hda.py b/openpype/hosts/houdini/plugins/publish/extract_hda.py index 301dd4e297..7dd03a92b7 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_hda.py +++ b/openpype/hosts/houdini/plugins/publish/extract_hda.py @@ -4,10 +4,11 @@ import os from pprint import pformat import pyblish.api -import openpype.api + +from openpype.pipeline import publish -class ExtractHDA(openpype.api.Extractor): +class ExtractHDA(publish.Extractor): order = pyblish.api.ExtractorOrder label = "Extract HDA" diff --git a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py index c754d60c59..ca9be64a47 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py @@ -1,11 +1,12 @@ import os import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.houdini.api.lib import render_rop -class ExtractRedshiftProxy(openpype.api.Extractor): +class ExtractRedshiftProxy(publish.Extractor): order = pyblish.api.ExtractorOrder + 0.1 label = "Extract Redshift Proxy" diff --git a/openpype/hosts/houdini/plugins/publish/extract_usd.py b/openpype/hosts/houdini/plugins/publish/extract_usd.py index 0fc26900fb..78c32affb4 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_usd.py +++ b/openpype/hosts/houdini/plugins/publish/extract_usd.py @@ -1,11 +1,12 @@ import os import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.houdini.api.lib import render_rop -class ExtractUSD(openpype.api.Extractor): +class ExtractUSD(publish.Extractor): order = pyblish.api.ExtractorOrder label = "Extract USD" diff --git a/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py b/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py index 80919c023b..f686f712bb 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py +++ b/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py @@ -5,7 +5,6 @@ import sys from collections import deque import pyblish.api -import openpype.api from openpype.client import ( get_asset_by_name, @@ -16,6 +15,7 @@ from openpype.client import ( from openpype.pipeline import ( get_representation_path, legacy_io, + publish, ) import openpype.hosts.houdini.api.usd as hou_usdlib from openpype.hosts.houdini.api.lib import render_rop @@ -160,7 +160,7 @@ def parm_values(overrides): parm.set(value) -class ExtractUSDLayered(openpype.api.Extractor): +class ExtractUSDLayered(publish.Extractor): order = pyblish.api.ExtractorOrder label = "Extract Layered USD" diff --git a/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py b/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py index 113e1b0bcb..26ec423048 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py +++ b/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py @@ -1,11 +1,12 @@ import os import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.houdini.api.lib import render_rop -class ExtractVDBCache(openpype.api.Extractor): +class ExtractVDBCache(publish.Extractor): order = pyblish.api.ExtractorOrder + 0.1 label = "Extract VDB Cache" From b61688828e4e65433e7d7aabb68883601b62244a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:11:07 +0200 Subject: [PATCH 169/513] Use new import source of Extractor --- .../hosts/maya/plugins/publish/extract_ass.py | 6 ++--- .../maya/plugins/publish/extract_assembly.py | 7 +++-- .../maya/plugins/publish/extract_assproxy.py | 6 ++--- .../plugins/publish/extract_camera_alembic.py | 4 +-- .../publish/extract_camera_mayaScene.py | 4 +-- .../hosts/maya/plugins/publish/extract_fbx.py | 6 ++--- .../maya/plugins/publish/extract_layout.py | 27 ++++++++++--------- .../maya/plugins/publish/extract_look.py | 8 +++--- .../plugins/publish/extract_maya_scene_raw.py | 5 ++-- .../maya/plugins/publish/extract_model.py | 4 +-- .../publish/extract_multiverse_look.py | 4 +-- .../plugins/publish/extract_multiverse_usd.py | 4 +-- .../publish/extract_multiverse_usd_comp.py | 4 +-- .../publish/extract_multiverse_usd_over.py | 4 +-- .../maya/plugins/publish/extract_playblast.py | 6 ++--- .../plugins/publish/extract_pointcache.py | 4 +-- .../plugins/publish/extract_redshift_proxy.py | 4 +-- .../plugins/publish/extract_rendersetup.py | 7 ++--- .../hosts/maya/plugins/publish/extract_rig.py | 4 +-- .../maya/plugins/publish/extract_thumbnail.py | 4 +-- .../publish/extract_unreal_skeletalmesh.py | 5 ++-- .../publish/extract_unreal_staticmesh.py | 5 ++-- .../maya/plugins/publish/extract_vrayproxy.py | 4 +-- .../maya/plugins/publish/extract_vrayscene.py | 4 +-- .../plugins/publish/extract_xgen_cache.py | 4 +-- .../plugins/publish/extract_yeti_cache.py | 4 +-- .../maya/plugins/publish/extract_yeti_rig.py | 4 +-- 27 files changed, 76 insertions(+), 76 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_ass.py index 760f410f91..5c21a4ff08 100644 --- a/openpype/hosts/maya/plugins/publish/extract_ass.py +++ b/openpype/hosts/maya/plugins/publish/extract_ass.py @@ -1,12 +1,12 @@ import os -import openpype.api - from maya import cmds + +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractAssStandin(openpype.api.Extractor): +class ExtractAssStandin(publish.Extractor): """Extract the content of the instance to a ass file Things to pay attention to: diff --git a/openpype/hosts/maya/plugins/publish/extract_assembly.py b/openpype/hosts/maya/plugins/publish/extract_assembly.py index 482930b76e..466fe962ab 100644 --- a/openpype/hosts/maya/plugins/publish/extract_assembly.py +++ b/openpype/hosts/maya/plugins/publish/extract_assembly.py @@ -1,14 +1,13 @@ +import os import json -import os - -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import extract_alembic from maya import cmds -class ExtractAssembly(openpype.api.Extractor): +class ExtractAssembly(publish.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, diff --git a/openpype/hosts/maya/plugins/publish/extract_assproxy.py b/openpype/hosts/maya/plugins/publish/extract_assproxy.py index 93720dbb82..4937a28a9e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_assproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_assproxy.py @@ -3,17 +3,17 @@ import contextlib from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractAssProxy(openpype.api.Extractor): +class ExtractAssProxy(publish.Extractor): """Extract proxy model as Maya Ascii to use as arnold standin """ - order = openpype.api.Extractor.order + 0.2 + order = publish.Extractor.order + 0.2 label = "Ass Proxy (Maya ASCII)" hosts = ["maya"] families = ["ass"] diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index b744bfd0fe..aa445a0387 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -2,11 +2,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api import lib -class ExtractCameraAlembic(openpype.api.Extractor): +class ExtractCameraAlembic(publish.Extractor): """Extract a Camera as Alembic. The cameras gets baked to world space by default. Only when the instance's diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 8d6c4b5f3c..7467fa027d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -5,7 +5,7 @@ import itertools from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api import lib @@ -78,7 +78,7 @@ def unlock(plug): cmds.disconnectAttr(source, destination) -class ExtractCameraMayaScene(openpype.api.Extractor): +class ExtractCameraMayaScene(publish.Extractor): """Extract a Camera as Maya Scene. This will create a duplicate of the camera that will be baked *with* diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx.py b/openpype/hosts/maya/plugins/publish/extract_fbx.py index fbbe8e06b0..9af3acef65 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx.py @@ -4,13 +4,13 @@ import os from maya import cmds # noqa import maya.mel as mel # noqa import pyblish.api -import openpype.api -from openpype.hosts.maya.api.lib import maintained_selection +from openpype.pipeline import publish +from openpype.hosts.maya.api.lib import maintained_selection from openpype.hosts.maya.api import fbx -class ExtractFBX(openpype.api.Extractor): +class ExtractFBX(publish.Extractor): """Extract FBX from Maya. This extracts reproducible FBX exports ignoring any of the diff --git a/openpype/hosts/maya/plugins/publish/extract_layout.py b/openpype/hosts/maya/plugins/publish/extract_layout.py index 991217684a..0f499b09b1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_layout.py +++ b/openpype/hosts/maya/plugins/publish/extract_layout.py @@ -5,13 +5,11 @@ import json from maya import cmds from maya.api import OpenMaya as om -from bson.objectid import ObjectId - -from openpype.pipeline import legacy_io -import openpype.api +from openpype.client import get_representation_by_id +from openpype.pipeline import legacy_io, publish -class ExtractLayout(openpype.api.Extractor): +class ExtractLayout(publish.Extractor): """Extract a layout.""" label = "Extract Layout" @@ -30,6 +28,8 @@ class ExtractLayout(openpype.api.Extractor): instance.data["representations"] = [] json_data = [] + # TODO representation queries can be refactored to be faster + project_name = legacy_io.active_project() for asset in cmds.sets(str(instance), query=True): # Find the container @@ -43,11 +43,11 @@ class ExtractLayout(openpype.api.Extractor): representation_id = cmds.getAttr(f"{container}.representation") - representation = legacy_io.find_one( - { - "type": "representation", - "_id": ObjectId(representation_id) - }, projection={"parent": True, "context.family": True}) + representation = get_representation_by_id( + project_name, + representation_id, + fields=["parent", "context.family"] + ) self.log.info(representation) @@ -102,9 +102,10 @@ class ExtractLayout(openpype.api.Extractor): for i in range(0, len(t_matrix_list), row_length): t_matrix.append(t_matrix_list[i:i + row_length]) - json_element["transform_matrix"] = [] - for row in t_matrix: - json_element["transform_matrix"].append(list(row)) + json_element["transform_matrix"] = [ + list(row) + for row in t_matrix + ] basis_list = [ 1, 0, 0, 0, diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index ce3b265566..91b0da75c6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -13,8 +13,8 @@ from maya import cmds # noqa import pyblish.api -import openpype.api -from openpype.pipeline import legacy_io +from openpype.lib import source_hash +from openpype.pipeline import legacy_io, publish from openpype.hosts.maya.api import lib # Modes for transfer @@ -161,7 +161,7 @@ def no_workspace_dir(): os.rmdir(fake_workspace_dir) -class ExtractLook(openpype.api.Extractor): +class ExtractLook(publish.Extractor): """Extract Look (Maya Scene + JSON) Only extracts the sets (shadingEngines and alike) alongside a .json file @@ -505,7 +505,7 @@ class ExtractLook(openpype.api.Extractor): args = [] if do_maketx: args.append("maketx") - texture_hash = openpype.api.source_hash(filepath, *args) + texture_hash = source_hash(filepath, *args) # If source has been published before with the same settings, # then don't reprocess but hardlink from the original diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py index 3a47cdadb5..3769ec3605 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -4,12 +4,11 @@ import os from maya import cmds -import openpype.api from openpype.hosts.maya.api.lib import maintained_selection -from openpype.pipeline import AVALON_CONTAINER_ID +from openpype.pipeline import AVALON_CONTAINER_ID, publish -class ExtractMayaSceneRaw(openpype.api.Extractor): +class ExtractMayaSceneRaw(publish.Extractor): """Extract as Maya Scene (raw). This will preserve all references, construction history, etc. diff --git a/openpype/hosts/maya/plugins/publish/extract_model.py b/openpype/hosts/maya/plugins/publish/extract_model.py index 0282d1e9c8..7c8c3a2981 100644 --- a/openpype/hosts/maya/plugins/publish/extract_model.py +++ b/openpype/hosts/maya/plugins/publish/extract_model.py @@ -4,11 +4,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api import lib -class ExtractModel(openpype.api.Extractor): +class ExtractModel(publish.Extractor): """Extract as Model (Maya Scene). Only extracts contents based on the original "setMembers" data to ensure diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py index 82e2b41929..92137acb95 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py @@ -2,11 +2,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractMultiverseLook(openpype.api.Extractor): +class ExtractMultiverseLook(publish.Extractor): """Extractor for Multiverse USD look data. This will extract: diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 3654be7b34..6c352bebe6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -3,11 +3,11 @@ import six from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractMultiverseUsd(openpype.api.Extractor): +class ExtractMultiverseUsd(publish.Extractor): """Extractor for Multiverse USD Asset data. This will extract settings for a Multiverse Write Asset operation: diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index ad9303657f..a62729c198 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -2,11 +2,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractMultiverseUsdComposition(openpype.api.Extractor): +class ExtractMultiverseUsdComposition(publish.Extractor): """Extractor of Multiverse USD Composition data. This will extract settings for a Multiverse Write Composition operation: diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index d44e3878b8..0628623e88 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -1,12 +1,12 @@ import os -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection from maya import cmds -class ExtractMultiverseUsdOverride(openpype.api.Extractor): +class ExtractMultiverseUsdOverride(publish.Extractor): """Extractor for Multiverse USD Override data. This will extract settings for a Multiverse Write Override operation: diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 871adda0c3..81fdba2f98 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -1,18 +1,16 @@ import os -import glob -import contextlib import clique import capture +from openpype.pipeline import publish from openpype.hosts.maya.api import lib -import openpype.api from maya import cmds import pymel.core as pm -class ExtractPlayblast(openpype.api.Extractor): +class ExtractPlayblast(publish.Extractor): """Extract viewport playblast. Takes review camera and creates review Quicktime video based on viewport diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index bf6feecef3..7c1c6d5c12 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -2,7 +2,7 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, @@ -11,7 +11,7 @@ from openpype.hosts.maya.api.lib import ( ) -class ExtractAlembic(openpype.api.Extractor): +class ExtractAlembic(publish.Extractor): """Produce an alembic of just point positions and normals. Positions and normals, uvs, creases are preserved, but nothing more, diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index 23cac9190d..4377275635 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -4,11 +4,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractRedshiftProxy(openpype.api.Extractor): +class ExtractRedshiftProxy(publish.Extractor): """Extract the content of the instance to a redshift proxy file.""" label = "Redshift Proxy (.rs)" diff --git a/openpype/hosts/maya/plugins/publish/extract_rendersetup.py b/openpype/hosts/maya/plugins/publish/extract_rendersetup.py index 6bdd5f590e..5970c038a4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rendersetup.py +++ b/openpype/hosts/maya/plugins/publish/extract_rendersetup.py @@ -1,10 +1,11 @@ -import json import os -import openpype.api +import json + import maya.app.renderSetup.model.renderSetup as renderSetup +from openpype.pipeline import publish -class ExtractRenderSetup(openpype.api.Extractor): +class ExtractRenderSetup(publish.Extractor): """ Produce renderSetup template file diff --git a/openpype/hosts/maya/plugins/publish/extract_rig.py b/openpype/hosts/maya/plugins/publish/extract_rig.py index 53c1eeb671..c71a2f710d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig.py @@ -4,11 +4,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractRig(openpype.api.Extractor): +class ExtractRig(publish.Extractor): """Extract rig as Maya Scene.""" label = "Extract Rig (Maya Scene)" diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 9380da5128..854301ea48 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -3,14 +3,14 @@ import glob import capture +from openpype.pipeline import publish from openpype.hosts.maya.api import lib -import openpype.api from maya import cmds import pymel.core as pm -class ExtractThumbnail(openpype.api.Extractor): +class ExtractThumbnail(publish.Extractor): """Extract viewport thumbnail. Takes review camera and creates a thumbnail based on viewport diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index 7ef7f2f181..258120db2f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -6,7 +6,8 @@ from contextlib import contextmanager from maya import cmds # noqa import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.maya.api import fbx @@ -20,7 +21,7 @@ def renamed(original_name, renamed_name): cmds.rename(renamed_name, original_name) -class ExtractUnrealSkeletalMesh(openpype.api.Extractor): +class ExtractUnrealSkeletalMesh(publish.Extractor): """Extract Unreal Skeletal Mesh as FBX from Maya. """ order = pyblish.api.ExtractorOrder - 0.1 diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 69d51f9ff1..44f0615a27 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -5,7 +5,8 @@ import os from maya import cmds # noqa import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( parent_nodes, maintained_selection @@ -13,7 +14,7 @@ from openpype.hosts.maya.api.lib import ( from openpype.hosts.maya.api import fbx -class ExtractUnrealStaticMesh(openpype.api.Extractor): +class ExtractUnrealStaticMesh(publish.Extractor): """Extract Unreal Static Mesh as FBX from Maya. """ order = pyblish.api.ExtractorOrder - 0.1 diff --git a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py index 562ca078e1..38bf02245a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py @@ -2,11 +2,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractVRayProxy(openpype.api.Extractor): +class ExtractVRayProxy(publish.Extractor): """Extract the content of the instance to a vrmesh file Things to pay attention to: diff --git a/openpype/hosts/maya/plugins/publish/extract_vrayscene.py b/openpype/hosts/maya/plugins/publish/extract_vrayscene.py index 5d41697e5f..8442df1611 100644 --- a/openpype/hosts/maya/plugins/publish/extract_vrayscene.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayscene.py @@ -3,14 +3,14 @@ import os import re -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.render_setup_tools import export_in_rs_layer from openpype.hosts.maya.api.lib import maintained_selection from maya import cmds -class ExtractVrayscene(openpype.api.Extractor): +class ExtractVrayscene(publish.Extractor): """Extractor for vrscene.""" label = "VRay Scene (.vrscene)" diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py b/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py index 5728682abe..77350f343e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py @@ -2,14 +2,14 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( suspended_refresh, maintained_selection ) -class ExtractXgenCache(openpype.api.Extractor): +class ExtractXgenCache(publish.Extractor): """Produce an alembic of just xgen interactive groom """ diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index cf6db00e9a..b61f599cab 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -3,10 +3,10 @@ import json from maya import cmds -import openpype.api +from openpype.pipeline import publish -class ExtractYetiCache(openpype.api.Extractor): +class ExtractYetiCache(publish.Extractor): """Producing Yeti cache files using scene time range. This will extract Yeti cache file sequence and fur settings. diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index 6e21bffa4e..1d0c5e88c3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -7,7 +7,7 @@ import contextlib from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api import lib @@ -90,7 +90,7 @@ def yetigraph_attribute_values(assumed_destination, resources): pass -class ExtractYetiRig(openpype.api.Extractor): +class ExtractYetiRig(publish.Extractor): """Extract the Yeti rig to a Maya Scene and write the Yeti rig data.""" label = "Extract Yeti Rig" From da8697d8821d9396d4a401e80be1ae6cdeb460b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:52:30 +0200 Subject: [PATCH 170/513] Use new import source of Extractor --- .../hosts/nuke/plugins/publish/extract_backdrop.py | 4 ++-- openpype/hosts/nuke/plugins/publish/extract_camera.py | 5 +++-- openpype/hosts/nuke/plugins/publish/extract_gizmo.py | 4 ++-- openpype/hosts/nuke/plugins/publish/extract_model.py | 5 +++-- .../hosts/nuke/plugins/publish/extract_render_local.py | 10 ++++++---- .../hosts/nuke/plugins/publish/extract_review_data.py | 7 ++++--- .../nuke/plugins/publish/extract_review_data_lut.py | 5 +++-- .../nuke/plugins/publish/extract_review_data_mov.py | 7 ++++--- .../hosts/nuke/plugins/publish/extract_slate_frame.py | 4 ++-- .../hosts/nuke/plugins/publish/extract_thumbnail.py | 5 +++-- 10 files changed, 32 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_backdrop.py b/openpype/hosts/nuke/plugins/publish/extract_backdrop.py index 0a2df0898e..d1e5c4cc5a 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/extract_backdrop.py @@ -4,7 +4,7 @@ import nuke import pyblish.api -import openpype +from openpype.pipeline import publish from openpype.hosts.nuke.api.lib import ( maintained_selection, reset_selection, @@ -12,7 +12,7 @@ from openpype.hosts.nuke.api.lib import ( ) -class ExtractBackdropNode(openpype.api.Extractor): +class ExtractBackdropNode(publish.Extractor): """Extracting content of backdrop nodes Will create nuke script only with containing nodes. diff --git a/openpype/hosts/nuke/plugins/publish/extract_camera.py b/openpype/hosts/nuke/plugins/publish/extract_camera.py index 54f65a0be3..b751bfab03 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_camera.py +++ b/openpype/hosts/nuke/plugins/publish/extract_camera.py @@ -5,11 +5,12 @@ from pprint import pformat import nuke import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractCamera(openpype.api.Extractor): +class ExtractCamera(publish.Extractor): """ 3D camera exctractor """ label = 'Exctract Camera' diff --git a/openpype/hosts/nuke/plugins/publish/extract_gizmo.py b/openpype/hosts/nuke/plugins/publish/extract_gizmo.py index 2d5bfdeb5e..3047ad6724 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_gizmo.py +++ b/openpype/hosts/nuke/plugins/publish/extract_gizmo.py @@ -3,7 +3,7 @@ import nuke import pyblish.api -import openpype +from openpype.pipeline import publish from openpype.hosts.nuke.api import utils as pnutils from openpype.hosts.nuke.api.lib import ( maintained_selection, @@ -12,7 +12,7 @@ from openpype.hosts.nuke.api.lib import ( ) -class ExtractGizmo(openpype.api.Extractor): +class ExtractGizmo(publish.Extractor): """Extracting Gizmo (Group) node Will create nuke script only with the Gizmo node. diff --git a/openpype/hosts/nuke/plugins/publish/extract_model.py b/openpype/hosts/nuke/plugins/publish/extract_model.py index 0375263338..d82cb3110b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_model.py +++ b/openpype/hosts/nuke/plugins/publish/extract_model.py @@ -2,14 +2,15 @@ import os from pprint import pformat import nuke import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.nuke.api.lib import ( maintained_selection, select_nodes ) -class ExtractModel(openpype.api.Extractor): +class ExtractModel(publish.Extractor): """ 3D model exctractor """ label = 'Exctract Model' diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 8879f0c999..843d588786 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -1,11 +1,13 @@ -import pyblish.api -import nuke import os -import openpype + +import pyblish.api import clique +import nuke + +from openpype.pipeline import publish -class NukeRenderLocal(openpype.api.Extractor): +class NukeRenderLocal(publish.Extractor): # TODO: rewrite docstring to nuke """Render the current Nuke composition locally. diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data.py b/openpype/hosts/nuke/plugins/publish/extract_review_data.py index 38a8140cff..3c85b21b08 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data.py @@ -1,10 +1,11 @@ import os -import pyblish.api -import openpype from pprint import pformat +import pyblish.api + +from openpype.pipeline import publish -class ExtractReviewData(openpype.api.Extractor): +class ExtractReviewData(publish.Extractor): """Extracts review tag into available representation """ diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py index 4cf2fd7d9f..67779e9599 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py @@ -1,11 +1,12 @@ import os import pyblish.api -import openpype + +from openpype.pipeline import publish from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractReviewDataLut(openpype.api.Extractor): +class ExtractReviewDataLut(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index fc16e189fb..3fcfc2a4b5 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -1,13 +1,14 @@ import os -from pprint import pformat import re +from pprint import pformat import pyblish.api -import openpype + +from openpype.pipeline import publish from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractReviewDataMov(openpype.api.Extractor): +class ExtractReviewDataMov(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index b5cad143db..e7197b4fa8 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -6,7 +6,7 @@ import copy import pyblish.api import six -import openpype +from openpype.pipeline import publish from openpype.hosts.nuke.api import ( maintained_selection, duplicate_node, @@ -14,7 +14,7 @@ from openpype.hosts.nuke.api import ( ) -class ExtractSlateFrame(openpype.api.Extractor): +class ExtractSlateFrame(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index 2a919051d2..19eae9638b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -2,7 +2,8 @@ import sys import os import nuke import pyblish.api -import openpype + +from openpype.pipeline import publish from openpype.hosts.nuke.api import ( maintained_selection, get_view_process_node @@ -13,7 +14,7 @@ if sys.version_info[0] >= 3: unicode = str -class ExtractThumbnail(openpype.api.Extractor): +class ExtractThumbnail(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py From 72f254dd036c9bc1484a9d6d5092278e464d22a8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:54:01 +0200 Subject: [PATCH 171/513] Use new import source of Extractor --- openpype/hosts/photoshop/api/README.md | 6 +++--- .../photoshop/plugins/publish/extract_image.py | 4 ++-- .../photoshop/plugins/publish/extract_review.py | 15 +++++++++------ .../plugins/publish/extract_save_scene.py | 6 +++--- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/photoshop/api/README.md b/openpype/hosts/photoshop/api/README.md index 80792a4da0..4a36746cb2 100644 --- a/openpype/hosts/photoshop/api/README.md +++ b/openpype/hosts/photoshop/api/README.md @@ -127,11 +127,11 @@ class CollectInstances(pyblish.api.ContextPlugin): ```python import os -import openpype.api -from avalon import photoshop +from openpype.pipeline import publish +from openpype.hosts.photoshop import api as photoshop -class ExtractImage(openpype.api.Extractor): +class ExtractImage(publish.Extractor): """Produce a flattened image file from instance This plug-in takes into account only the layers in the group. diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py index a133e33409..c84a958960 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -1,10 +1,10 @@ import os -import openpype.api +from openpype.pipeline import publish from openpype.hosts.photoshop import api as photoshop -class ExtractImage(openpype.api.Extractor): +class ExtractImage(publish.Extractor): """Produce a flattened image file from instance This plug-in takes into account only the layers in the group. diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index 5d37c86ed8..e5fee311f8 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -2,12 +2,15 @@ import os import shutil from PIL import Image -import openpype.api -import openpype.lib +from openpype.lib import ( + run_subprocess, + get_ffmpeg_tool_path, +) +from openpype.pipeline import publish from openpype.hosts.photoshop import api as photoshop -class ExtractReview(openpype.api.Extractor): +class ExtractReview(publish.Extractor): """ Produce a flattened or sequence image files from all 'image' instances. @@ -72,7 +75,7 @@ class ExtractReview(openpype.api.Extractor): }) processed_img_names = [img_list] - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") instance.data["stagingDir"] = staging_dir @@ -93,7 +96,7 @@ class ExtractReview(openpype.api.Extractor): thumbnail_path ] self.log.debug("thumbnail args:: {}".format(args)) - output = openpype.lib.run_subprocess(args) + output = run_subprocess(args) instance.data["representations"].append({ "name": "thumbnail", @@ -116,7 +119,7 @@ class ExtractReview(openpype.api.Extractor): mov_path ] self.log.debug("mov args:: {}".format(args)) - output = openpype.lib.run_subprocess(args) + output = run_subprocess(args) self.log.debug(output) instance.data["representations"].append({ "name": "mov", diff --git a/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py b/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py index 03086f389f..aa900fec9f 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py @@ -1,11 +1,11 @@ -import openpype.api +from openpype.pipeline import publish from openpype.hosts.photoshop import api as photoshop -class ExtractSaveScene(openpype.api.Extractor): +class ExtractSaveScene(publish.Extractor): """Save scene before extraction.""" - order = openpype.api.Extractor.order - 0.49 + order = publish.Extractor.order - 0.49 label = "Extract Save Scene" hosts = ["photoshop"] families = ["workfile"] From 1f06830a03fa58ccfa2ccf76f940ec0281343c9c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:24:20 +0200 Subject: [PATCH 172/513] change both 'user' and 'username' keys --- openpype/plugins/publish/extract_burnin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 88093fb92f..36a28beb5d 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -492,6 +492,7 @@ class ExtractBurnin(openpype.api.Extractor): # OPENPYPE_USERNAME might have side effects webpublish_user_name = os.environ.get("WEBPUBLISH_OPENPYPE_USERNAME") if webpublish_user_name: + burnin_data["user"] = webpublish_user_name burnin_data["username"] = webpublish_user_name self.log.debug( From 398325684961bf9b408da0e56011cfbd4eb8bed1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:24:33 +0200 Subject: [PATCH 173/513] add 'user' to representation context data --- openpype/plugins/publish/integrate.py | 2 +- openpype/plugins/publish/integrate_hero_version.py | 2 +- openpype/plugins/publish/integrate_legacy.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index f99c718f8a..788966878f 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -135,7 +135,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # the database even if not used by the destination template db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "username", "output" + "family", "hierarchy", "username", "user", "output" ] skip_host_families = [] diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 7d698ff98d..5b8b141e88 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -46,7 +46,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): ignored_representation_names = [] db_representation_context_keys = [ "project", "asset", "task", "subset", "representation", - "family", "hierarchy", "task", "username" + "family", "hierarchy", "task", "username", "user" ] # QUESTION/TODO this process should happen on server if crashed due to # permissions error on files (files were used or user didn't have perms) diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index b90b61f587..5a6190f38e 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -127,7 +127,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): exclude_families = ["render.farm"] db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "task", "username" + "family", "hierarchy", "task", "username", "user" ] default_template_name = "publish" From 198ad3ac901dc962ab840a0b09a5bd25d4a286e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:25:02 +0200 Subject: [PATCH 174/513] add both 'user' and 'username' in integrate slack --- .../modules/slack/plugins/publish/integrate_slack_api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index c3b288f0cd..4a8e9f773f 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -95,13 +95,15 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): Reviews might be large, so allow only adding link to message instead of uploading only. """ + fill_data = copy.deepcopy(instance.context.data["anatomyData"]) + username = fill_data.get("user") fill_pairs = [ ("asset", instance.data.get("asset", fill_data.get("asset"))), ("subset", instance.data.get("subset", fill_data.get("subset"))), - ("username", instance.data.get("username", - fill_data.get("username"))), + ("user", username), + ("username", username), ("app", instance.data.get("app", fill_data.get("app"))), ("family", instance.data.get("family", fill_data.get("family"))), ("version", str(instance.data.get("version", From 6bcbf34d27e3806ae7f62a09d7d5a6002671a2eb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:53:39 +0200 Subject: [PATCH 175/513] changed class name to 'CollectUsernameForWebpublish' --- openpype/modules/ftrack/plugins/publish/collect_username.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/collect_username.py b/openpype/modules/ftrack/plugins/publish/collect_username.py index a9b746ea51..0e232bf83e 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/ftrack/plugins/publish/collect_username.py @@ -13,7 +13,7 @@ import os import pyblish.api -class CollectUsername(pyblish.api.ContextPlugin): +class CollectUsernameForWebpublish(pyblish.api.ContextPlugin): """ Translates user email to Ftrack username. @@ -32,10 +32,8 @@ class CollectUsername(pyblish.api.ContextPlugin): hosts = ["webpublisher", "photoshop"] targets = ["remotepublish", "filespublish", "tvpaint_worker"] - _context = None - def process(self, context): - self.log.info("CollectUsername") + self.log.info("{}".format(self.__class__.__name__)) os.environ["FTRACK_API_USER"] = os.environ["FTRACK_BOT_API_USER"] os.environ["FTRACK_API_KEY"] = os.environ["FTRACK_BOT_API_KEY"] From 8e6b0567c967008b0b64151eeceed23a426ed073 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:54:29 +0200 Subject: [PATCH 176/513] skip 'WEBPUBLISH_OPENPYPE_USERNAME' usage --- .../modules/ftrack/plugins/publish/collect_username.py | 1 - openpype/plugins/publish/extract_burnin.py | 7 ------- 2 files changed, 8 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/collect_username.py b/openpype/modules/ftrack/plugins/publish/collect_username.py index 0e232bf83e..ab1f7d8d5d 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/ftrack/plugins/publish/collect_username.py @@ -65,5 +65,4 @@ class CollectUsernameForWebpublish(pyblish.api.ContextPlugin): burnin_name = username if '@' in burnin_name: burnin_name = burnin_name[:burnin_name.index('@')] - os.environ["WEBPUBLISH_OPENPYPE_USERNAME"] = burnin_name context.data["user"] = burnin_name diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 36a28beb5d..c8b2b73874 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -488,13 +488,6 @@ class ExtractBurnin(openpype.api.Extractor): "frame_end_handle": frame_end_handle } - # use explicit username for webpublishes as rewriting - # OPENPYPE_USERNAME might have side effects - webpublish_user_name = os.environ.get("WEBPUBLISH_OPENPYPE_USERNAME") - if webpublish_user_name: - burnin_data["user"] = webpublish_user_name - burnin_data["username"] = webpublish_user_name - self.log.debug( "Basic burnin_data: {}".format(json.dumps(burnin_data, indent=4)) ) From 3ac5305f65b17083b126eeb76675064311bf4f4a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 13:54:58 +0200 Subject: [PATCH 177/513] better user query --- .../modules/ftrack/plugins/publish/collect_username.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/collect_username.py b/openpype/modules/ftrack/plugins/publish/collect_username.py index ab1f7d8d5d..798f3960a8 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_username.py +++ b/openpype/modules/ftrack/plugins/publish/collect_username.py @@ -1,5 +1,8 @@ """Loads publishing context from json and continues in publish process. +Should run before 'CollectAnatomyContextData' so the user on context is +changed before it's stored to context anatomy data or instance anatomy data. + Requires: anatomy -> context["anatomy"] *(pyblish.api.CollectorOrder - 0.11) @@ -52,12 +55,14 @@ class CollectUsernameForWebpublish(pyblish.api.ContextPlugin): return session = ftrack_api.Session(auto_connect_event_hub=False) - user = session.query("User where email like '{}'".format(user_email)) + user = session.query( + "User where email like '{}'".format(user_email) + ).first() if not user: raise ValueError( "Couldn't find user with {} email".format(user_email)) - user = user[0] + username = user.get("username") self.log.debug("Resolved ftrack username:: {}".format(username)) os.environ["FTRACK_API_USER"] = username From 3c6b999d544d65a8d31cc37ae54c731ff098f73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Thu, 1 Sep 2022 17:22:14 +0200 Subject: [PATCH 178/513] Kitsu - sync_all_project - add list ignore_projects --- openpype/modules/kitsu/utils/update_op_with_zou.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 8d65591c0b..f013251bb1 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -318,13 +318,13 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects(login: str, password: str): +def sync_all_projects(login: str, password: str, ignore_projects: list = []): """Update all OP projects in DB with Zou data. Args: login (str): Kitsu user login password (str): Kitsu user password - + ignore_projects (list): List of unsynced project names Raises: gazu.exception.AuthFailedException: Wrong user login and/or password """ @@ -340,7 +340,8 @@ def sync_all_projects(login: str, password: str): dbcon.install() all_projects = gazu.project.all_open_projects() for project in all_projects: - sync_project_from_kitsu(dbcon, project) + if project["name"] not in ignore_projects: + sync_project_from_kitsu(dbcon, project) def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): From d90f9fb5b819e874012141391861b843fc4abc82 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Sep 2022 18:12:16 +0200 Subject: [PATCH 179/513] Fix - changed format of version string in pyproject.toml Since Poetry 1.2 version in .toml must follow PEP-440, in our case there must not be dash sign. Plus sign is valid though. Version in pyproject.toml is not used anywhere yet (only maybe when uploading wheel to pip, which is not really used). --- pyproject.toml | 2 +- tools/ci_tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9ed1872eac..0deb4f465b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.2-nightly.1" # OpenPype +version = "3.14.2+nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" diff --git a/tools/ci_tools.py b/tools/ci_tools.py index 4c59cd6af6..65a615b33e 100644 --- a/tools/ci_tools.py +++ b/tools/ci_tools.py @@ -101,7 +101,7 @@ def bump_file_versions(version): # bump pyproject.toml filename = "pyproject.toml" - regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" + regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(+((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" pyproject_version = f"version = \"{version}\" # OpenPype" file_regex_replace(filename, regex, pyproject_version) From 05043b8578e8fcfa4cae58609fc1c5055a1f019c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Sep 2022 18:15:49 +0200 Subject: [PATCH 180/513] Fix - escaped plus sign --- tools/ci_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci_tools.py b/tools/ci_tools.py index 65a615b33e..c8f0cd48b4 100644 --- a/tools/ci_tools.py +++ b/tools/ci_tools.py @@ -101,7 +101,7 @@ def bump_file_versions(version): # bump pyproject.toml filename = "pyproject.toml" - regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(+((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" + regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(\+((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" pyproject_version = f"version = \"{version}\" # OpenPype" file_regex_replace(filename, regex, pyproject_version) From 4fc90655d7e5b6869650638e4b7ff064ebc2271e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Sep 2022 18:59:20 +0200 Subject: [PATCH 181/513] Fix - update cx-freeze Old version caused "No module named 'distutils.command.bdist_msi'" error --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 21b6bda880..726b248f8c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -339,7 +339,7 @@ test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", [[package]] name = "cx-freeze" -version = "6.9" +version = "6.11.1" description = "Create standalone executables from Python scripts" category = "dev" optional = false From 8bb526ed8f3f76e2fa1745e9b724ef9ba4051224 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 2 Sep 2022 08:48:30 +0200 Subject: [PATCH 182/513] nuke: validate write node is not failing due wrong type --- .../hosts/nuke/plugins/publish/validate_write_nodes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index 5a8bc2022e..26a563b13b 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -2,7 +2,8 @@ import pyblish.api from openpype.pipeline.publish import get_errored_instances_from_context from openpype.hosts.nuke.api.lib import ( get_write_node_template_attr, - set_node_knobs_from_settings + set_node_knobs_from_settings, + color_gui_to_int ) from openpype.pipeline import PublishXmlValidationError @@ -76,8 +77,11 @@ class ValidateNukeWriteNode(pyblish.api.InstancePlugin): # fix type differences if type(node_value) in (int, float): - value = float(value) - node_value = float(node_value) + if isinstance(value, list): + value = color_gui_to_int(value) + else: + value = float(value) + node_value = float(node_value) else: value = str(value) node_value = str(node_value) From 7ed5f5a0ee56d6481d28f0510d1bc6311e89ac38 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 11:04:13 +0200 Subject: [PATCH 183/513] Fix - update cx-freeze Old version caused "No module named 'distutils.command.bdist_msi'" error --- poetry.lock | 990 ++++++++++++++++++++++++++++++++++++++++++------- pyproject.toml | 3 +- 2 files changed, 864 insertions(+), 129 deletions(-) diff --git a/poetry.lock b/poetry.lock index 726b248f8c..b428393a1f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -33,7 +33,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} yarl = ">=1.0,<2.0" [package.extras] -speedups = ["aiodns", "brotli", "cchardet"] +speedups = ["Brotli", "aiodns", "cchardet"] [[package]] name = "aiohttp-json-rpc" @@ -122,6 +122,7 @@ python-versions = ">=3.6.2" [package.dependencies] lazy-object-proxy = ">=1.4.0" +setuptools = ">=20.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} wrapt = ">=1.11,<2" @@ -162,10 +163,10 @@ optional = false python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "autopep8" @@ -273,9 +274,9 @@ optional = false python-versions = ">=2.7, <4.0" [package.extras] -dev = ["sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)", "lowdown (>=0.2.0,<1)", "pytest-runner (>=2.7,<3)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)"] -doc = ["sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)", "lowdown (>=0.2.0,<1)"] -test = ["pytest-runner (>=2.7,<3)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)"] +dev = ["lowdown (>=0.2.0,<1)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)", "pytest-runner (>=2.7,<3)", "sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)"] +doc = ["lowdown (>=0.2.0,<1)", "sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)"] +test = ["pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)", "pytest-runner (>=2.7,<3)"] [[package]] name = "colorama" @@ -330,30 +331,39 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] [[package]] -name = "cx-freeze" +name = "cx-Freeze" version = "6.11.1" description = "Create standalone executables from Python scripts" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} -importlib-metadata = ">=4.3.1" +cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\" and python_version < \"3.10\""} +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +lief = {version = ">=0.11.5", markers = "sys_platform == \"win32\" and python_version <= \"3.10\""} +packaging = ">=21.0" +patchelf = {version = ">=0.12", markers = "sys_platform == \"linux\""} +setuptools = ">=59.0.1,<=60.10.0" + +[package.extras] +dev = ["bump2version (>=1.0.1)", "cibuildwheel (==2.6.1)", "pre-commit (>=2.17.0)", "pylint (>=2.13.0)"] +doc = ["sphinx (>=5.0.1,<6.0.0)", "sphinx-rtd-theme (==1.0.0)"] +test = ["nose (==1.3.7)", "pygments (>=2.11.2)", "pytest (>=7.0.1)", "pytest-cov (==3.0.0)", "pytest-mock (>=3.6.1)", "pytest-timeout (>=1.4.2)"] [[package]] name = "cx-logging" version = "3.0" description = "Python and C interfaces for logging" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -369,7 +379,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" wrapt = ">=1.10,<2" [package.extras] -dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] +dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] [[package]] name = "dill" @@ -391,8 +401,8 @@ optional = false python-versions = ">=3.6,<4.0" [package.extras] -dnssec = ["cryptography (>=2.6,<37.0)"] curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] +dnssec = ["cryptography (>=2.6,<37.0)"] doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.20)"] @@ -503,7 +513,7 @@ requests = ">=2.25.1,<=2.27.1" [package.extras] dev = ["wheel"] -test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] +test = ["black (==21.12b0)", "pre-commit (==2.17.0)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "pytest-cov (==2.12.1)", "requests-mock (==1.9.3)"] [[package]] name = "gitdb" @@ -576,7 +586,7 @@ rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} six = ">=1.9.0" [package.extras] -aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"] enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -648,9 +658,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" @@ -669,10 +679,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] +requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] name = "jedi" @@ -697,8 +707,8 @@ optional = false python-versions = ">=3.7" [package.extras] -trio = ["async-generator", "trio"] -test = ["async-timeout", "trio", "testpath", "pytest-asyncio (>=0.17)", "pytest-trio", "pytest"] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] [[package]] name = "jinja2" @@ -751,8 +761,8 @@ pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-enabler", "pytest-flake8", "pytest-mypy"] [[package]] name = "lazy-object-proxy" @@ -762,6 +772,14 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "lief" +version = "0.12.1" +description = "Library to instrument executable formats" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "log4mongo" version = "1.7.0" @@ -809,7 +827,7 @@ python-versions = "*" pyaaf2 = "1.4.0" [package.extras] -dev = ["check-manifest", "flake8 (>=3.5)", "coverage (>=4.5)", "urllib3 (>=1.24.3)"] +dev = ["check-manifest", "coverage (>=4.5)", "flake8 (>=3.5)", "urllib3 (>=1.24.3)"] view = ["PySide2 (>=5.11,<6.0)"] [package.source] @@ -821,7 +839,7 @@ reference = "openpype" name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -843,9 +861,9 @@ pynacl = ">=1.0.1" six = "*" [package.extras] -all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] -ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] -gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +all = ["bcrypt (>=3.1.3)", "gssapi (>=1.4.1)", "invoke (>=1.3)", "pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "pywin32 (>=2.1.8)"] +ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=1.3)"] [[package]] @@ -860,6 +878,17 @@ python-versions = ">=3.6" qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] +[[package]] +name = "patchelf" +version = "0.15.0.0" +description = "A small utility to modify the dynamic linker and RPATH of ELF executables." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["importlib-metadata", "pytest"] + [[package]] name = "pathlib2" version = "2.3.7.post1" @@ -892,8 +921,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" @@ -1054,7 +1083,7 @@ python-versions = "*" aws = ["pymongo-auth-aws (<2.0.0)"] encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"] gssapi = ["pykerberos"] -ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"] +ocsp = ["certifi", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] snappy = ["python-snappy"] srv = ["dnspython (>=1.16.0,<1.17.0)"] tls = ["ipaddress"] @@ -1072,8 +1101,8 @@ python-versions = ">=3.6" cffi = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] +docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pynput" @@ -1188,7 +1217,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytest-print" @@ -1381,6 +1410,19 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "setuptools" +version = "60.10.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-inline-tabs", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "shotgun-api3" version = "3.3.3" @@ -1413,8 +1455,8 @@ optional = false python-versions = ">=3.6.0" [package.extras] -optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] -testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] +optional = ["SQLAlchemy (>=1,<2)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=10,<11)"] +testing = ["Flask (>=1,<2)", "Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (<2)", "black (==22.3.0)", "boto3 (<=2)", "click (==8.0.4)", "codecov (>=2,<3)", "databases (>=0.5)", "flake8 (>=4,<5)", "itsdangerous (==1.1.0)", "moto (>=3,<4)", "psutil (>=5,<6)", "pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "pytest-cov (>=2,<3)"] [[package]] name = "smmap" @@ -1469,8 +1511,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed-ast"] [[package]] name = "sphinx-qt-documentation" @@ -1485,9 +1527,9 @@ docutils = "*" sphinx = "*" [package.extras] -test = ["pytest-cov", "pytest (>=3.0.0)"] -lint = ["pylint", "flake8", "black"] dev = ["pre-commit"] +lint = ["black", "flake8", "pylint"] +test = ["pytest (>=3.0.0)", "pytest-cov"] [[package]] name = "sphinx-rtd-theme" @@ -1502,7 +1544,7 @@ docutils = "<0.18" sphinx = ">=1.6" [package.extras] -dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] [[package]] name = "sphinxcontrib-applehelp" @@ -1513,7 +1555,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -1525,7 +1567,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -1537,8 +1579,8 @@ optional = false python-versions = ">=3.6" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest", "html5lib"] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" @@ -1549,7 +1591,7 @@ optional = false python-versions = ">=3.5" [package.extras] -test = ["pytest", "flake8", "mypy"] +test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" @@ -1560,7 +1602,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -1572,7 +1614,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -1588,7 +1630,7 @@ sphinxcontrib-serializinghtml = "*" [package.extras] lint = ["flake8"] -test = ["pytest", "sqlalchemy", "whoosh", "sphinx"] +test = ["Sphinx", "pytest", "sqlalchemy", "whoosh"] [[package]] name = "stone" @@ -1659,8 +1701,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -1682,6 +1724,17 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] six = "*" +[[package]] +name = "wheel" +version = "0.37.1" +description = "A built-package format for Python" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.extras] +test = ["pytest (>=3.0.0)", "pytest-cov"] + [[package]] name = "wrapt" version = "1.14.1" @@ -1703,8 +1756,8 @@ aiohttp = "<4" yarl = "*" [package.extras] -develop = ["async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov", "sphinx", "sphinxcontrib-plantuml", "tox (>=2.4)"] -testing = ["async-timeout", "pytest", "pytest-aiohttp", "pytest-cov", "coverage (!=4.3)", "coveralls"] +develop = ["Sphinx", "async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov", "sphinxcontrib-plantuml", "tox (>=2.4)"] +testing = ["async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov"] ujson = ["ujson"] [[package]] @@ -1729,13 +1782,13 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "de7422afb6aed02f75e1696afdda9ad6c7bf32da76b5022ee3e8f71a1ac4bae2" +content-hash = "6a5f6910109c9ed6fb1cc1cb1cef7b21a0055a17c393175a1a7aabc00e35d54f" [metadata.files] acre = [] @@ -1817,7 +1870,10 @@ aiohttp-json-rpc = [ {file = "aiohttp-json-rpc-0.13.3.tar.gz", hash = "sha256:6237a104478c22c6ef96c7227a01d6832597b414e4b79a52d85593356a169e99"}, {file = "aiohttp_json_rpc-0.13.3-py3-none-any.whl", hash = "sha256:4fbd197aced61bd2df7ae3237ead7d3e08833c2ccf48b8581e1828c95ebee680"}, ] -aiohttp-middlewares = [] +aiohttp-middlewares = [ + {file = "aiohttp-middlewares-2.1.0.tar.gz", hash = "sha256:5863970d944dc63faedc96ef324a7fe2bcefefebe29acc90cd641236322d00c3"}, + {file = "aiohttp_middlewares-2.1.0-py3-none-any.whl", hash = "sha256:c83d48702e6a8669981976f39a60e83d059dc01d7b1ee651aec5d4cb807ff784"}, +] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, @@ -1835,7 +1891,10 @@ arrow = [ {file = "arrow-0.17.0-py2.py3-none-any.whl", hash = "sha256:e098abbd9af3665aea81bdd6c869e93af4feb078e98468dd351c383af187aac5"}, {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] -astroid = [] +astroid = [ + {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"}, + {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"}, +] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1844,21 +1903,112 @@ asynctest = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, ] -atomicwrites = [] -attrs = [] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] autopep8 = [ {file = "autopep8-1.5.7-py2.py3-none-any.whl", hash = "sha256:aa213493c30dcdac99537249ee65b24af0b2c29f2e83cd8b3f68760441ed0db9"}, {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] -babel = [] -bcrypt = [] +babel = [ + {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, + {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, +] +bcrypt = [ + {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, + {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, + {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, + {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, + {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, + {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, +] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] -cachetools = [] -certifi = [] -cffi = [] +cachetools = [ + {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, + {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, +] +certifi = [ + {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, + {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, +] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, @@ -1871,7 +2021,10 @@ clique = [ {file = "clique-1.6.1-py2.py3-none-any.whl", hash = "sha256:8619774fa035661928dd8c93cd805acf2d42533ccea1b536c09815ed426c9858"}, {file = "clique-1.6.1.tar.gz", hash = "sha256:90165c1cf162d4dd1baef83ceaa1afc886b453e379094fa5b60ea470d1733e66"}, ] -colorama = [] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] commonmark = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, @@ -1880,20 +2033,99 @@ coolname = [ {file = "coolname-1.1.0-py2.py3-none-any.whl", hash = "sha256:e6a83a0ac88640f4f3d2070438dbe112fe80cfebc119c93bd402976ec84c0978"}, {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] -coverage = [] -cryptography = [] -cx-freeze = [ - {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, - {file = "cx_Freeze-6.9-cp310-cp310-win_amd64.whl", hash = "sha256:243f36d35a034a409cd6247d8cb5d1fbfd7374e3e668e813d0811f64d6bd5ed3"}, - {file = "cx_Freeze-6.9-cp36-cp36m-win32.whl", hash = "sha256:ffc855eabc735b693e2d604d71dce6d52d78a6ba1070c55d51e786dd68ed232c"}, - {file = "cx_Freeze-6.9-cp36-cp36m-win_amd64.whl", hash = "sha256:fe4e32a0c75b2b54491882926bf3ba12f8a3d589822a68a8be7c09f1dcca5546"}, - {file = "cx_Freeze-6.9-cp37-cp37m-win32.whl", hash = "sha256:99c292e7a31cb343efc0cf47f82220a44a4a3b8776651624cd8ee03c23104940"}, - {file = "cx_Freeze-6.9-cp37-cp37m-win_amd64.whl", hash = "sha256:738ab22f3a3f6bc220b16dccf2aa0603c3cd271b2a7a9d9480dab82311308b23"}, - {file = "cx_Freeze-6.9-cp38-cp38-win32.whl", hash = "sha256:c1c75df572858e623d0aa39771cd984c0abd8aacb43b2aca2d12d0bc95f25566"}, - {file = "cx_Freeze-6.9-cp38-cp38-win_amd64.whl", hash = "sha256:0788c895c47fdcf375151ce78ff42336c01aca7bc43daecb8f8f8356cdc42b43"}, - {file = "cx_Freeze-6.9-cp39-cp39-win32.whl", hash = "sha256:a31f5ddbc80b29e297370d868791470b0e3e9062db45038c23293a76ed039018"}, - {file = "cx_Freeze-6.9-cp39-cp39-win_amd64.whl", hash = "sha256:30708f603076713c0a839cdfb34f4126d68e9d61afb3d9a59daa9cf252033872"}, - {file = "cx_Freeze-6.9.tar.gz", hash = "sha256:673aa3199af2ef87fc03a43a30e5d78b27ced2cedde925da89c55b5657da267b"}, +coverage = [ + {file = "coverage-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f50d3a822947572496ea922ee7825becd8e3ae6fbd2400cd8236b7d64b17f285"}, + {file = "coverage-6.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5191d53afbe5b6059895fa7f58223d3751c42b8101fb3ce767e1a0b1a1d8f87"}, + {file = "coverage-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04010af3c06ce2bfeb3b1e4e05d136f88d88c25f76cd4faff5d1fd84d11581ea"}, + {file = "coverage-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6630d8d943644ea62132789940ca97d05fac83f73186eaf0930ffa715fbdab6b"}, + {file = "coverage-6.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05de0762c1caed4a162b3e305f36cf20a548ff4da0be6766ad5c870704be3660"}, + {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e3a41aad5919613483aad9ebd53336905cab1bd6788afd3995c2a972d89d795"}, + {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a2738ba1ee544d6f294278cfb6de2dc1f9a737a780469b5366e662a218f806c3"}, + {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a0d2df4227f645a879010461df2cea6b7e3fb5a97d7eafa210f7fb60345af9e8"}, + {file = "coverage-6.4.3-cp310-cp310-win32.whl", hash = "sha256:73a10939dc345460ca0655356a470dd3de9759919186a82383c87b6eb315faf2"}, + {file = "coverage-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:53c8edd3b83a4ddba3d8c506f1359401e7770b30f2188f15c17a338adf5a14db"}, + {file = "coverage-6.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1eda5cae434282712e40b42aaf590b773382afc3642786ac3ed39053973f61f"}, + {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59fc88bc13e30f25167e807b8cad3c41b7218ef4473a20c86fd98a7968733083"}, + {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75314b00825d70e1e34b07396e23f47ed1d4feedc0122748f9f6bd31a544840"}, + {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52f8b9fcf3c5e427d51bbab1fb92b575a9a9235d516f175b24712bcd4b5be917"}, + {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5a559aab40c716de80c7212295d0dc96bc1b6c719371c20dd18c5187c3155518"}, + {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:306788fd019bb90e9cbb83d3f3c6becad1c048dd432af24f8320cf38ac085684"}, + {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:920a734fe3d311ca01883b4a19aa386c97b82b69fbc023458899cff0a0d621b9"}, + {file = "coverage-6.4.3-cp37-cp37m-win32.whl", hash = "sha256:ab9ef0187d6c62b09dec83a84a3b94f71f9690784c84fd762fb3cf2d2b44c914"}, + {file = "coverage-6.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:39ebd8e120cb77a06ee3d5fc26f9732670d1c397d7cd3acf02f6f62693b89b80"}, + {file = "coverage-6.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc698580216050b5f4a34d2cdd2838b429c53314f1c4835fab7338200a8396f2"}, + {file = "coverage-6.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:877ee5478fd78e100362aed56db47ccc5f23f6e7bb035a8896855f4c3e49bc9b"}, + {file = "coverage-6.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:555a498999c44f5287cc95500486cd0d4f021af9162982cbe504d4cb388f73b5"}, + {file = "coverage-6.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff095a5aac7011fdb51a2c82a8fae9ec5211577f4b764e1e59cfa27ceeb1b59"}, + {file = "coverage-6.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de1e9335e2569974e20df0ce31493d315a830d7987e71a24a2a335a8d8459d3"}, + {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7856ea39059d75f822ff0df3a51ea6d76307c897048bdec3aad1377e4e9dca20"}, + {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:411fdd9f4203afd93b056c0868c8f9e5e16813e765de962f27e4e5798356a052"}, + {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cdf7b83f04a313a21afb1f8730fe4dd09577fefc53bbdfececf78b2006f4268e"}, + {file = "coverage-6.4.3-cp38-cp38-win32.whl", hash = "sha256:ab2b1a89d2bc7647622e9eaf06128a5b5451dccf7c242deaa31420b055716481"}, + {file = "coverage-6.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:0e34247274bde982bbc613894d33f9e36358179db2ed231dd101c48dd298e7b0"}, + {file = "coverage-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b104b6b1827d6a22483c469e3983a204bcf9c6bf7544bf90362c4654ebc2edf3"}, + {file = "coverage-6.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adf1a0d272633b21d645dd6e02e3293429c1141c7d65a58e4cbcd592d53b8e01"}, + {file = "coverage-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff9832434a9193fbd716fbe05f9276484e18d26cc4cf850853594bb322807ac3"}, + {file = "coverage-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:923f9084d7e1d31b5f74c92396b05b18921ed01ee5350402b561a79dce3ea48d"}, + {file = "coverage-6.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d64304acf79766e650f7acb81d263a3ea6e2d0d04c5172b7189180ff2c023c"}, + {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fc294de50941d3da66a09dca06e206297709332050973eca17040278cb0918ff"}, + {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a42eaaae772f14a5194f181740a67bfd48e8806394b8c67aa4399e09d0d6b5db"}, + {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4822327b35cb032ff16af3bec27f73985448f08e874146b5b101e0e558b613dd"}, + {file = "coverage-6.4.3-cp39-cp39-win32.whl", hash = "sha256:f217850ac0e046ede611312703423767ca032a7b952b5257efac963942c055de"}, + {file = "coverage-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0a84376e4fd13cebce2c0ef8c2f037929c8307fb94af1e5dbe50272a1c651b5d"}, + {file = "coverage-6.4.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:068d6f2a893af838291b8809c876973d885543411ea460f3e6886ac0ee941732"}, + {file = "coverage-6.4.3.tar.gz", hash = "sha256:ec2ae1f398e5aca655b7084392d23e80efb31f7a660d2eecf569fb9f79b3fb94"}, +] +cryptography = [ + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, + {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, + {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, + {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, +] +cx-Freeze = [ + {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e372b9e72ac0e2207ee65a9d404e2669da1134dc37f5ace9a2a779099d3aa868"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd293382e1ad270dddf5a2707db5dbb8600a1e0b0c9b0da7af9d61326eb1b325"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:feec2f36bce042da6a0d92690bc592b0dcec29218adc2278535cd13b28ec3485"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5aafcc6337856d5921b20f41acdcc8d0fe770388f3a072eb25163f8825f6c5d"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-win32.whl", hash = "sha256:b99cc0b6d6c1ba51bd9fe11dbfae9aabcf089ba779ea86d83d280e2e40f484e7"}, + {file = "cx_Freeze-6.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:f0dfe6acf25eb096faba7d4b4b001bcd0f818e372ea1f05d900665b0ad82b0b9"}, + {file = "cx_Freeze-6.11.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3a68e70dcb27b0720b131a35c5fdd096012fe00119a8e51d935f3fb3cd251c39"}, + {file = "cx_Freeze-6.11.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7bde925042d8843af9b6242a1bf3865dbbae088f3183a89a575124ec2e14a4"}, + {file = "cx_Freeze-6.11.1-cp36-cp36m-win32.whl", hash = "sha256:7698fb82b6f84b3426774b5f3bee770601f26b612306319664a02f1ec5160861"}, + {file = "cx_Freeze-6.11.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9848c975401b21a98aa896baabfed067c3e981afd5b5b0a8a5eabe5c9f23d3c5"}, + {file = "cx_Freeze-6.11.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87dcf5ceb78dc6af910c45238128fda2394b7c430d3fa469e87e1efdeeb5d4cc"}, + {file = "cx_Freeze-6.11.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb74d8cc1f8c658986acc19ea6875b985a979421f9bb9c310b43cd2ff5d90c44"}, + {file = "cx_Freeze-6.11.1-cp37-cp37m-win32.whl", hash = "sha256:971c0a8356ef0ee09a3097f9c9d5b52cde6d08d1ef80e997eb4a6e22fe0eff2f"}, + {file = "cx_Freeze-6.11.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7c1cb44379b2093cbdde77e302a376f29aa61999c73be6e8a559463db84b85c4"}, + {file = "cx_Freeze-6.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc90d6dbde66e8ddfe6b26f63fb2ea7d6d0e4568205f40660a63b8b200dcabcf"}, + {file = "cx_Freeze-6.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f56f618a23d86bdcfff22b29ec993117effd32a401060013105517301c0bf32"}, + {file = "cx_Freeze-6.11.1-cp38-cp38-win32.whl", hash = "sha256:4edfb5d65afb11eb9f0326d40d15445366481585705b3096f2cd090e30a36247"}, + {file = "cx_Freeze-6.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:cfb5a8032bf424c04814c9426425fa1db4cf8c280da948969eead9f616c0fd92"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0a3e32980269cfabc2e814978bfdf4382fe3cbc9ac64f9f1bdb1cd2ddf3a40d0"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:798bb7ca037c3c885efd3eda6756c84c7927c712b730b22a7f256440faa36d38"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5aa1759098ca4853200a79138b626a9caa2ccf829d662b28c82ec7e71ea97cde"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7962680ae04ee3afda1012026b5394a534e2526b68681d591158b7d8bc733bcf"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-win32.whl", hash = "sha256:da4f82fe27e71571c0ab9d700b5e6c6c631ae39133d9b6d7157939f1e9f37312"}, + {file = "cx_Freeze-6.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:aaf399b6ed5d54b7271980ae354605620bedcd52d722f57ad527bd989c56a875"}, + {file = "cx_Freeze-6.11.1.tar.gz", hash = "sha256:8f3a30c9e3394f290655e346d3b460910656b30ac6347a87499bb5ad365c6e7c"}, ] cx-logging = [ {file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"}, @@ -1910,8 +2142,14 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] -deprecated = [] -dill = [] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +dill = [ + {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, + {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, +] dnspython = [ {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, @@ -1920,22 +2158,93 @@ docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] -dropbox = [] +dropbox = [ + {file = "dropbox-11.33.0-py2-none-any.whl", hash = "sha256:3ee9024631b80f18938556d5e27cbdede26d6dc0b73aeaa90fc075ce96c950b1"}, + {file = "dropbox-11.33.0-py3-none-any.whl", hash = "sha256:1a0cbc22b0d1dae96e18b37e3520e5c289de7eb1303935db40e4dbfc9bb9e59b"}, + {file = "dropbox-11.33.0.tar.gz", hash = "sha256:7c638b521169a460de38b9eaeb204fe918874f72d6c3eed005d064b6f37da9c1"}, +] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] -evdev = [] +evdev = [ + {file = "evdev-1.6.0.tar.gz", hash = "sha256:ecfa01b5c84f7e8c6ced3367ac95288f43cd84efbfd7dd7d0cdbfc0d18c87a6a"}, +] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] -frozenlist = [] -ftrack-python-api = [] +frozenlist = [ + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"}, + {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"}, + {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"}, + {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"}, + {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"}, + {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"}, + {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"}, + {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"}, + {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, +] +ftrack-python-api = [ + {file = "ftrack-python-api-2.3.3.tar.gz", hash = "sha256:358f37e5b1c5635eab107c19e27a0c890d512877f78af35b1ac416e90c037295"}, + {file = "ftrack_python_api-2.3.3-py2.py3-none-any.whl", hash = "sha256:82834c4d5def5557a2ea547a7e6f6ba84d3129e8f90457d8bbd85b287a2c39f6"}, +] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] -gazu = [] +gazu = [ + {file = "gazu-0.8.30-py2.py3-none-any.whl", hash = "sha256:d692927a11314151bc33e7d67edee634053f70a3b09e4500dfc6626bfea18753"}, +] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, @@ -1944,24 +2253,42 @@ gitpython = [ {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] -google-api-core = [] +google-api-core = [ + {file = "google-api-core-2.8.2.tar.gz", hash = "sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc"}, + {file = "google_api_core-2.8.2-py3-none-any.whl", hash = "sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50"}, +] google-api-python-client = [ {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] -google-auth = [] +google-auth = [ + {file = "google-auth-2.10.0.tar.gz", hash = "sha256:7904dbd44b745c7323fef29565adee2fe7ff48473e2d94443aced40b0404a395"}, + {file = "google_auth-2.10.0-py2.py3-none-any.whl", hash = "sha256:1deba4a54f95ef67b4139eaf5c20eaa7047215eec9f6a2344599b8596db8863b"}, +] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] -googleapis-common-protos = [] +googleapis-common-protos = [ + {file = "googleapis-common-protos-1.56.4.tar.gz", hash = "sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417"}, + {file = "googleapis_common_protos-1.56.4-py2.py3-none-any.whl", hash = "sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394"}, +] httplib2 = [ {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] -idna = [] -imagesize = [] -importlib-metadata = [] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +imagesize = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, +] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -1974,12 +2301,18 @@ jedi = [ {file = "jedi-0.13.3-py2.py3-none-any.whl", hash = "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"}, {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] -jeepney = [] +jeepney = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] -jinxed = [] +jinxed = [ + {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, + {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, +] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, {file = "jsonschema-2.6.0.tar.gz", hash = "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"}, @@ -2027,16 +2360,58 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] +lief = [ + {file = "lief-0.12.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:4fbbc9d520de87ac22210c62d22a9b088e5460f9a028741311e6f68ef8877ddd"}, + {file = "lief-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443e4494df448ea1a021976258c7a6aca27d81b0612783fa3a84fab196fb9fcb"}, + {file = "lief-0.12.1-cp310-cp310-win32.whl", hash = "sha256:1c4019dddf03a5185462fb5ea04327cee08d40f46777b02f0773c7dc294552ea"}, + {file = "lief-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d7e09968f99ddf1e3983d3bcc16c62d1b6635a345fee8d8139f82b31bad457d6"}, + {file = "lief-0.12.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9fa6269ec4fa3f874b807fbba3c48a46af30df2497723f6966080e3eb630cb26"}, + {file = "lief-0.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b05cac5fa491e01e1819573bbbbcaea0a4229f4aa3a2edb231b5695ddaf2d"}, + {file = "lief-0.12.1-cp36-cp36m-win32.whl", hash = "sha256:f1292bff96579c18e01e20b7a14043052379fe6e9a476c1d6d88aca43e5f9ac7"}, + {file = "lief-0.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dab63876113bd573d64ce043f50153f6e2810e5e78256397aa0fe1fedf82ab84"}, + {file = "lief-0.12.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:5771f5226b62c885a7aa30c1b98040d39229a1dab889d03155e5538e57d0054b"}, + {file = "lief-0.12.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8ec307a762505076a6d31566225a231c44ec7063c0e7d751ac4654c674454c47"}, + {file = "lief-0.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a755f6088d3b2041e4402adf917ac87e5ad9d1c5278973f48a29a5631fe393eb"}, + {file = "lief-0.12.1-cp37-cp37m-win32.whl", hash = "sha256:5d746f7eb6d3bf35a0230c7184aaaf434cb1ea89d7e7c8e8fe14a49cf2bb17a0"}, + {file = "lief-0.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2d3ab7212da696bcbe5ca9dd78ceaa32dfb8a0e85e18001793b4441ef4624561"}, + {file = "lief-0.12.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:4360b0acd525ba77777cc38f0e5128c90c93cc4e91ab566ef3aa45b7f8a8c57e"}, + {file = "lief-0.12.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5e82e466d36cbabb28cc1a787b554d2feae5ab55c39cab58ef64fb6513bad92a"}, + {file = "lief-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa0022a3bf70ef46335639e61b946cc2d9cf012d60e263c215e3e64b1ce38b4"}, + {file = "lief-0.12.1-cp38-cp38-win32.whl", hash = "sha256:d29f91d9f64f67d3ada5b7e0e48ab084d825fb4601d32d9fecdd2bdf23cdad23"}, + {file = "lief-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:7dea6b3f17d362f93165379c46dadb012c73b1f751c8ceac256e5f43842cd86d"}, + {file = "lief-0.12.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:44012da4c32c670a97bb8a055a4ff16168cfaa757d03986f319aa3329a43e343"}, + {file = "lief-0.12.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e1d23997b0a71d34e766ff183be07854c6f698fd3d6aa44bf30b6b7f4f77ef55"}, + {file = "lief-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b845eca79c772041efb38b50cfaf951e24bc047ec462450b7e54e75b7e2bee0d"}, + {file = "lief-0.12.1-cp39-cp39-win32.whl", hash = "sha256:0df84ac2df20b14db12e69442d39b0e8cd89428ba3b131995e0570bcd3725460"}, + {file = "lief-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:960a2da9f28c8d5dba753bb9ab77e26b3c6ff9b9658918be95650ceb8ee91e68"}, + {file = "lief-0.12.1.zip", hash = "sha256:4ff4ccfae2e1ee4ccba2b5556027dbb56282b8a973c5835c5b597e8b7b664416"}, +] log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -2045,14 +2420,27 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -2062,6 +2450,12 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -2131,22 +2525,109 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] -opentimelineio = [] +opentimelineio = [ + {file = "OpenTimelineIO-0.14.0.dev1-cp310-cp310-linux_x86_64.whl", hash = "sha256:112c27ad419a79c88cd4ebac96278ab8f446fda4c6e1a70c871f2b24b3d003ef"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e2416ca76805a07ecbcdda65e5a31ce447e04e2db7082d72582740cbd8a16d7"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8806dc240496b72e16a0fddacb0b2e825d19656d80689098e6c5bd6a805bc84"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp36-cp36m-win_amd64.whl", hash = "sha256:0aa54488ca50b53ac247610cef23fb63619dd1993016c0cd4069e54526d5905c"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-linux_x86_64.whl", hash = "sha256:8b11287eb733ad1c7fc53d4af3e3f926c396add6c3a3c1417b9c2b001f7ef4ba"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:2cba2ce567fc06f042365393dbe8e99dc4c1361999fb7ddc03d4b8b0d5ddb894"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:3d96da307c1969c309974a6734c7f3e39925236c845c8289f25d4d7d00be3f0c"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:968cb0c5e6e7d697037b1cd4f7707521995a32fc51664139ed15004b93ab8106"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dae64aa418193ca094854a55efa8bcc5a5c2855b3679509987e8b48610d31"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-win_amd64.whl", hash = "sha256:85dfae42f5a992ef85d0015f33f999751d846a484ef907e8834407d545a7ee6a"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aaac346cb758de719f88ac175d3948409ded39c5eed0844068402f70f3e90b6"}, + {file = "OpenTimelineIO-0.14.0.dev1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a09f6dd199218cf69faf42a8b21f5be2cd01764e57dbcc8456b3ced564eb110f"}, + {file = "OpenTimelineIO-0.14.0.dev1.tar.gz", hash = "sha256:d29eafd5188c3ad6c7b6d2095bf69984d590c331b701d3bbc644d7abd5f08606"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -paramiko = [] +paramiko = [ + {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, + {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, +] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] +patchelf = [ + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:08e5e30a9415a8628de47726fbf15bfcd89be35df51c8a0a12372aebd0c5b4f6"}, + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:4ce9d08119816bc4316c8ecc5f33da42384934fc0fc9cfbdded53a4930705466"}, + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.musllinux_1_1_s390x.whl", hash = "sha256:ae19b0f91aabc9af2608a4ca0395533f1df9122e6abc11ef2c8db6e4db0f98c2"}, + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.musllinux_1_1_i686.whl", hash = "sha256:f3f87aee44d1d1b2209e38c4227b0316bb03538df68d20b3d96205aa87868d95"}, + {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:52e48c08110f2988a9761a5a383f7ae35b1e8e06a140e320d18386d3510697ed"}, + {file = "patchelf-0.15.0.0.tar.gz", hash = "sha256:0f8dcf0df0ba919ce37e8aef67a08bde5326897098451df94ab3a5eedc9e08d9"}, +] pathlib2 = [ {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] -pillow = [] -platformdirs = [] +pillow = [ + {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, + {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, + {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, + {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, + {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, + {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, + {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, + {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, + {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, + {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, + {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, + {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, + {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, + {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, + {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, + {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, + {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, + {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -2159,7 +2640,22 @@ prefixed = [ {file = "prefixed-0.3.2-py2.py3-none-any.whl", hash = "sha256:5e107306462d63f2f03c529dbf11b0026fdfec621a9a008ca639d71de22995c3"}, {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] -protobuf = [] +protobuf = [ + {file = "protobuf-4.21.5-cp310-abi3-win32.whl", hash = "sha256:5310cbe761e87f0c1decce019d23f2101521d4dfff46034f8a12a53546036ec7"}, + {file = "protobuf-4.21.5-cp310-abi3-win_amd64.whl", hash = "sha256:e5c5a2886ae48d22a9d32fbb9b6636a089af3cd26b706750258ce1ca96cc0116"}, + {file = "protobuf-4.21.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ee04f5823ed98bb9a8c3b1dc503c49515e0172650875c3f76e225b223793a1f2"}, + {file = "protobuf-4.21.5-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:b04484d6f42f48c57dd2737a72692f4c6987529cdd148fb5b8e5f616862a2e37"}, + {file = "protobuf-4.21.5-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e0b272217aad8971763960238c1a1e6a65d50ef7824e23300da97569a251c55"}, + {file = "protobuf-4.21.5-cp37-cp37m-win32.whl", hash = "sha256:5eb0724615e90075f1d763983e708e1cef08e66b1891d8b8b6c33bc3b2f1a02b"}, + {file = "protobuf-4.21.5-cp37-cp37m-win_amd64.whl", hash = "sha256:011c0f267e85f5d73750b6c25f0155d5db1e9443cd3590ab669a6221dd8fcdb0"}, + {file = "protobuf-4.21.5-cp38-cp38-win32.whl", hash = "sha256:7b6f22463e2d1053d03058b7b4ceca6e4ed4c14f8c286c32824df751137bf8e7"}, + {file = "protobuf-4.21.5-cp38-cp38-win_amd64.whl", hash = "sha256:b52e7a522911a40445a5f588bd5b5e584291bfc5545e09b7060685e4b2ff814f"}, + {file = "protobuf-4.21.5-cp39-cp39-win32.whl", hash = "sha256:a7faa62b183d6a928e3daffd06af843b4287d16ef6e40f331575ecd236a7974d"}, + {file = "protobuf-4.21.5-cp39-cp39-win_amd64.whl", hash = "sha256:5e0ce02418ef03d7657a420ae8fd6fec4995ac713a3cb09164e95f694dbcf085"}, + {file = "protobuf-4.21.5-py2.py3-none-any.whl", hash = "sha256:bf711b451212dc5b0fa45ae7dada07d8e71a4b0ff0bc8e4783ee145f47ac4f82"}, + {file = "protobuf-4.21.5-py3-none-any.whl", hash = "sha256:3ec6f5b37935406bb9df9b277e79f8ed81d697146e07ef2ba8a5a272fb24b2c9"}, + {file = "protobuf-4.21.5.tar.gz", hash = "sha256:eb1106e87e095628e96884a877a51cdb90087106ee693925ec0a300468a9be3a"}, +] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -2218,8 +2714,14 @@ pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] -pygments = [] -pylint = [] +pygments = [ + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, +] +pylint = [ + {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, + {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, +] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, {file = "pymongo-3.12.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a055d29f1302892a9389a382bed10a3f77708bcf3e49bfb76f7712fa5f391cc6"}, @@ -2346,10 +2848,42 @@ pynput = [ {file = "pynput-1.7.6-py3.9.egg", hash = "sha256:264429fbe676e98e9050ad26a7017453bdd08768adb25cafb918347cf9f1eb4a"}, {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] -pyobjc-core = [] -pyobjc-framework-applicationservices = [] -pyobjc-framework-cocoa = [] -pyobjc-framework-quartz = [] +pyobjc-core = [ + {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, + {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, + {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, + {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, + {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, + {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, +] +pyobjc-framework-applicationservices = [ + {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, + {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, +] +pyobjc-framework-cocoa = [ + {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, + {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, + {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, + {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, + {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, + {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, +] +pyobjc-framework-quartz = [ + {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, + {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, + {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, + {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, + {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, + {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, +] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, @@ -2373,8 +2907,14 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-engineio = [] -python-socketio = [] +python-engineio = [ + {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, + {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, +] +python-socketio = [ + {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, + {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, +] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2382,7 +2922,10 @@ python-xlib = [ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] -pytz = [] +pytz = [ + {file = "pytz-2022.2-py2.py3-none-any.whl", hash = "sha256:d9b245e63af49c4e51afdec5402f56b99c0cb483a84a12bb8b7db980386baade"}, + {file = "pytz-2022.2.tar.gz", hash = "sha256:bc824559e43e8ab983426a49525079d186b25372ff63aa3430ccd527d95edc3a"}, +] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, {file = "pywin32-301-cp35-cp35m-win_amd64.whl", hash = "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72"}, @@ -2399,7 +2942,10 @@ pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] -"qt.py" = [] +"qt.py" = [ + {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, + {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, +] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, {file = "QtAwesome-0.7.3.tar.gz", hash = "sha256:b98b9038d19190e83ab26d91c4d8fc3a36591ee2bc7f5016d4438b8240d097bd"}, @@ -2412,19 +2958,35 @@ recommonmark = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] -requests = [] -rsa = [] -secretstorage = [] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +rsa = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] +secretstorage = [ + {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, + {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, +] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] +setuptools = [ + {file = "setuptools-60.10.0-py3-none-any.whl", hash = "sha256:782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96"}, + {file = "setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"}, +] shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -slack-sdk = [] +slack-sdk = [ + {file = "slack_sdk-3.18.1-py2.py3-none-any.whl", hash = "sha256:63ce5e6253a31873d6c921c9feaa842a93a2f56e6e009cb7daf406f4bc4df798"}, + {file = "slack_sdk-3.18.1.tar.gz", hash = "sha256:a25d3d2bf0bf605d54d764d4a463fe7c0659ee24c13d75653e2bec247bd5998b"}, +] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, @@ -2433,9 +2995,18 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] -speedcopy = [] -sphinx = [] -sphinx-qt-documentation = [] +speedcopy = [ + {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, + {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, +] +sphinx = [ + {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, + {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, +] +sphinx-qt-documentation = [ + {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, + {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, +] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, @@ -2484,13 +3055,44 @@ tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typed-ast = [] -typing-extensions = [] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] -urllib3 = [] +urllib3 = [ + {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, + {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, +] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, @@ -2499,10 +3101,142 @@ websocket-client = [ {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] -wrapt = [] +wheel = [ + {file = "wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a"}, + {file = "wheel-0.37.1.tar.gz", hash = "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4"}, +] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, {file = "wsrpc_aiohttp-3.2.0-py3-none-any.whl", hash = "sha256:fa9b0bf5cb056898cb5c9f64cbc5eacb8a5dd18ab1b7f0cd4a2208b4a7fde282"}, ] -yarl = [] -zipp = [] +yarl = [ + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, + {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, + {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, + {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, + {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, + {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, + {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, + {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, + {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, + {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, + {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, +] +zipp = [ + {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, + {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, +] diff --git a/pyproject.toml b/pyproject.toml index 0deb4f465b..2a0606a10c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,12 +70,13 @@ requests = "^2.25.1" pysftp = "^0.2.9" dropbox = "^11.20.0" aiohttp-middlewares = "^2.0.0" +cx-Freeze = "6.11.1" [tool.poetry.dev-dependencies] flake8 = "^3.7" autopep8 = "^1.4" coverage = "*" -cx_freeze = "~6.9" +cx_freeze = "^6.11.1" GitPython = "^3.1.17" jedi = "^0.13" Jinja2 = "^2.11" From 4bdd18cb817bbc58c1143e0e02442a9346ce9a1e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 11:50:14 +0200 Subject: [PATCH 184/513] Use DeadlineKeyValueVar for EnvironmentKeyValue on Job Info - To improve readability of code that sets the values --- .../deadline/abstract_submit_deadline.py | 58 +++++++++++++++---- .../publish/submit_aftereffects_deadline.py | 11 ++-- .../publish/submit_harmony_deadline.py | 10 ++-- .../plugins/publish/submit_maya_deadline.py | 7 +-- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 35b114da95..beb1cd0fae 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -67,6 +67,43 @@ def requests_get(*args, **kwargs): return requests.get(*args, **kwargs) +class DeadlineKeyValueVar(dict): + """ + + Serializes dictionary key values as "{key}={value}" like Deadline uses + for EnvironmentKeyValue. + + As an example: + EnvironmentKeyValue0="A_KEY=VALUE_A" + EnvironmentKeyValue1="OTHER_KEY=VALUE_B" + + The keys are serialized in alphabetical order (sorted). + + Example: + >>> var = DeadlineKeyValueVar("EnvironmentKeyValue") + >>> var["my_var"] = "hello" + >>> var["my_other_var"] = "hello2" + >>> var.serialize() + + + """ + def __init__(self, key): + super(DeadlineKeyValueVar, self).__init__() + self.__key = key + + def serialize(self): + key = self.__key + + # Allow custom location for index in serialized string + if "{}" not in key: + key = key + "{}" + + return { + key.format(index): "{}={}".format(var_key, var_value) + for index, (var_key, var_value) in enumerate(sorted(self.items())) + } + + class DeadlineIndexedVar(dict): """ @@ -80,15 +117,9 @@ class DeadlineIndexedVar(dict): """ def __init__(self, key): + super(DeadlineIndexedVar, self).__init__() self.__key = key - def next_available_index(self): - # Add as first unused entry - i = 0 - while i in self.keys(): - i += 1 - return i - def serialize(self): key = self.__key @@ -100,6 +131,13 @@ class DeadlineIndexedVar(dict): key.format(index): value for index, value in sorted(self.items()) } + def next_available_index(self): + # Add as first unused entry + i = 0 + while i in self.keys(): + i += 1 + return i + def update(self, data): # Force the integer key check for key, value in data.items(): @@ -271,7 +309,7 @@ class DeadlineJobInfo(object): # Environment # ---------------------------------------------- - EnvironmentKeyValue = attr.ib(factory=partial(DeadlineIndexedVar, + EnvironmentKeyValue = attr.ib(factory=partial(DeadlineKeyValueVar, "EnvironmentKeyValue")) IncludeEnvironment = attr.ib(default=None) # Default: false @@ -281,7 +319,7 @@ class DeadlineJobInfo(object): # Job Extra Info # ---------------------------------------------- ExtraInfo = attr.ib(factory=partial(DeadlineIndexedVar, "ExtraInfo")) - ExtraInfoKeyValue = attr.ib(factory=partial(DeadlineIndexedVar, + ExtraInfoKeyValue = attr.ib(factory=partial(DeadlineKeyValueVar, "ExtraInfoKeyValue")) # Task Extra Info Names @@ -326,7 +364,7 @@ class DeadlineJobInfo(object): """ def filter_data(a, v): - if isinstance(v, DeadlineIndexedVar): + if isinstance(v, (DeadlineIndexedVar, DeadlineKeyValueVar)): return False if v is None: return False diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index 1d68793d53..55acd92043 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -92,13 +92,12 @@ class AfterEffectsSubmitDeadline( environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) for key in keys: - val = environment.get(key) - if val: - dln_job_info.EnvironmentKeyValue = "{key}={value}".format( - key=key, - value=val) + value = environment.get(key) + if value: + dln_job_info.EnvironmentKeyValue[key] = value + # to recognize job from PYPE for turning Event On/Off - dln_job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + dln_job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" return dln_job_info diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 3f9c09b592..6327143623 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -284,14 +284,12 @@ class HarmonySubmitDeadline( environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) for key in keys: - val = environment.get(key) - if val: - job_info.EnvironmentKeyValue = "{key}={value}".format( - key=key, - value=val) + value = environment.get(key) + if value: + job_info.EnvironmentKeyValue[key] = value # to recognize job from PYPE for turning Event On/Off - job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" return job_info diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 9692b136e9..ad46feea03 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -137,8 +137,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for key, value in environment.items(): if not value: continue - job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, - value=value) + job_info.EnvironmentKeyValue[key] = value # Adding file dependencies. if self.asset_dependencies: @@ -538,9 +537,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for key, value in envs.items(): if not value: continue - - job_info.EnvironmentKeyValue = "{key}={value}".format(key=key, - value=value) + job_info.EnvironmentKeyValue[key] = value plugin_info.update({ "Version": "3.6", From 1e87c9d6d2c7338f8e53e8a06d9f1983056797b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 12:02:39 +0200 Subject: [PATCH 185/513] Use DeadlineIndexedVar `__iadd__` functionality --- .../plugins/publish/submit_aftereffects_deadline.py | 4 ++-- .../deadline/plugins/publish/submit_maya_deadline.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index 55acd92043..0c1ffa6bd7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -67,9 +67,9 @@ class AfterEffectsSubmitDeadline( dln_job_info.Group = self.group dln_job_info.Department = self.department dln_job_info.ChunkSize = self.chunk_size - dln_job_info.OutputFilename = \ + dln_job_info.OutputFilename += \ os.path.basename(self._instance.data["expectedFiles"][0]) - dln_job_info.OutputDirectory = \ + dln_job_info.OutputDirectory += \ os.path.dirname(self._instance.data["expectedFiles"][0]) dln_job_info.JobDelay = "00:00:00" diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index ad46feea03..6b08f9894d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -144,14 +144,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): dependencies = instance.context.data["fileDependencies"] dependencies.append(context.data["currentFile"]) for dependency in dependencies: - job_info.AssetDependency = dependency + job_info.AssetDependency += dependency # Add list of expected files to job # --------------------------------- exp = instance.data.get("expectedFiles") for filepath in self._iter_expected_files(exp): - job_info.OutputDirectory = os.path.dirname(filepath) - job_info.OutputFilename = os.path.basename(filepath) + job_info.OutputDirectory += os.path.dirname(filepath) + job_info.OutputFilename += os.path.basename(filepath) return job_info @@ -443,7 +443,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): if self.asset_dependencies: # Asset dependency to wait for at least the scene file to sync. - job_info.AssetDependency = self.scene_path + job_info.AssetDependency += self.scene_path # Get layer prefix render_products = self._instance.data["renderProducts"] From 2c01cb806d68aa04a733e16d4cfd1abb15f438fe Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 12:03:34 +0200 Subject: [PATCH 186/513] Remove backwards compatibility for append functionality in old style vars --- openpype/modules/deadline/abstract_submit_deadline.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index beb1cd0fae..f698b7688e 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -393,16 +393,6 @@ class DeadlineJobInfo(object): for key, value in data.items(): setattr(self, key, value) - def __setattr__(self, key, value): - # Backwards compatibility: Allow appending to index vars by setting - # it on Job Info directly like: JobInfo.OutputFilename = filename - existing = getattr(self, key, None) - if isinstance(existing, DeadlineIndexedVar): - existing += value - return - - object.__setattr__(self, key, value) - @six.add_metaclass(AbstractMetaInstancePlugin) class AbstractSubmitDeadline(pyblish.api.InstancePlugin): From 47164b36effa0f4986ccf15aa3a4967ccb014e26 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 12:11:35 +0200 Subject: [PATCH 187/513] Be more explicit about what keys to include from Session This way it matches more with logic of other host submitters (e.g. AfterEffects + Harmony) --- .../deadline/plugins/publish/submit_maya_deadline.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 6b08f9894d..bb48fe6902 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -130,15 +130,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) - # to recognize job from PYPE for turning Event On/Off - environment["OPENPYPE_RENDER_JOB"] = "1" - environment["OPENPYPE_LOG_NO_COLORS"] = "1" - - for key, value in environment.items(): + for key in keys: + value = environment.get(key) if not value: continue job_info.EnvironmentKeyValue[key] = value + # to recognize job from PYPE for turning Event On/Off + job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" + job_info.EnvironmentKeyValue["OPENPYPE_LOG_NO_COLORS"] = "1" + # Adding file dependencies. if self.asset_dependencies: dependencies = instance.context.data["fileDependencies"] From 37371936cf293200b74b7d5ee5381aac8a0551ba Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 2 Sep 2022 14:08:40 +0200 Subject: [PATCH 188/513] safer task data --- .../plugins/publish/integrate_slack_api.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 4a8e9f773f..643e55915b 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -112,13 +112,19 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if review_path: fill_pairs.append(("review_filepath", review_path)) - task_data = instance.data.get("task") - if not task_data: - task_data = fill_data.get("task") - for key, value in task_data.items(): - fill_key = "task[{}]".format(key) - fill_pairs.append((fill_key, value)) - fill_pairs.append(("task", task_data["name"])) + task_data = fill_data.get("task") + if task_data: + if ( + "{task}" in message_templ + or "{Task}" in message_templ + or "{TASK}" in message_templ + ): + fill_pairs.append(("task", task_data["name"])) + + else: + for key, value in task_data.items(): + fill_key = "task[{}]".format(key) + fill_pairs.append((fill_key, value)) self.log.debug("fill_pairs ::{}".format(fill_pairs)) multiple_case_variants = prepare_template_data(fill_pairs) From fc8d525207216b7ce8a47499f2419e1febd56f13 Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 14:14:33 +0200 Subject: [PATCH 189/513] Added an event triggered when user connects to kitsu --- openpype/modules/kitsu/utils/credentials.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index 0529380d6d..b80852dd6a 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -5,6 +5,7 @@ from typing import Tuple import gazu from openpype.lib.local_settings import OpenPypeSecureRegistry +from openpype.lib import emit_event def validate_credentials( @@ -32,6 +33,7 @@ def validate_credentials( except gazu.exception.AuthFailedException: return False + emit_on_kitsu_login(login) return True @@ -102,3 +104,13 @@ def set_credentials_envs(login: str, password: str): """ os.environ["KITSU_LOGIN"] = login os.environ["KITSU_PWD"] = password + +def emit_on_kitsu_login(login:str): + """Notifies listeners that Kitsu module succesfully connected, + and passes them data + + Args: + login (str): Kitsu username + """ + event_data = {"username": login} + emit_event("kitsu.user.logged", data = event_data, source = "kitsu") \ No newline at end of file From 75e2ec1a65316a0da8fa6b72530e684b6a77b4fe Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 15:44:47 +0200 Subject: [PATCH 190/513] Update credentials.py Linted, should fix hounds comments --- openpype/modules/kitsu/utils/credentials.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index b80852dd6a..a3b90a992b 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -8,9 +8,7 @@ from openpype.lib.local_settings import OpenPypeSecureRegistry from openpype.lib import emit_event -def validate_credentials( - login: str, password: str, kitsu_url: str = None -) -> bool: +def validate_credentials(login: str, password: str, kitsu_url: str = None) -> bool: """Validate credentials by trying to connect to Kitsu host URL. Args: @@ -105,12 +103,13 @@ def set_credentials_envs(login: str, password: str): os.environ["KITSU_LOGIN"] = login os.environ["KITSU_PWD"] = password -def emit_on_kitsu_login(login:str): - """Notifies listeners that Kitsu module succesfully connected, + +def emit_on_kitsu_login(login: str): + """Notifies listeners that Kitsu module succesfully connected, and passes them data Args: login (str): Kitsu username """ event_data = {"username": login} - emit_event("kitsu.user.logged", data = event_data, source = "kitsu") \ No newline at end of file + emit_event("kitsu.user.logged", data=event_data, source="kitsu") From 7a98e083de1f151d686601ca620c9432a1d756b8 Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 15:48:23 +0200 Subject: [PATCH 191/513] Shortened max line length to 79 Hopefully hounds will be happy --- openpype/modules/kitsu/utils/credentials.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index a3b90a992b..d853d5b437 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -8,7 +8,9 @@ from openpype.lib.local_settings import OpenPypeSecureRegistry from openpype.lib import emit_event -def validate_credentials(login: str, password: str, kitsu_url: str = None) -> bool: +def validate_credentials( + login: str, password: str, kitsu_url: str = None +) -> bool: """Validate credentials by trying to connect to Kitsu host URL. Args: From 74a404021975c52d6e22bf3ea5125fa23f9bcc3f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 15:49:18 +0200 Subject: [PATCH 192/513] Revert "Fix - update cx-freeze" This reverts commit 7ed5f5a0ee56d6481d28f0510d1bc6311e89ac38. --- poetry.lock | 990 +++++++------------------------------------------ pyproject.toml | 3 +- 2 files changed, 129 insertions(+), 864 deletions(-) diff --git a/poetry.lock b/poetry.lock index b428393a1f..726b248f8c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -33,7 +33,7 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] +speedups = ["aiodns", "brotli", "cchardet"] [[package]] name = "aiohttp-json-rpc" @@ -122,7 +122,6 @@ python-versions = ">=3.6.2" [package.dependencies] lazy-object-proxy = ">=1.4.0" -setuptools = ">=20.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} wrapt = ">=1.11,<2" @@ -163,10 +162,10 @@ optional = false python-versions = ">=3.5" [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "autopep8" @@ -274,9 +273,9 @@ optional = false python-versions = ">=2.7, <4.0" [package.extras] -dev = ["lowdown (>=0.2.0,<1)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)", "pytest-runner (>=2.7,<3)", "sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)"] -doc = ["lowdown (>=0.2.0,<1)", "sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)"] -test = ["pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)", "pytest-runner (>=2.7,<3)"] +dev = ["sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)", "lowdown (>=0.2.0,<1)", "pytest-runner (>=2.7,<3)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)"] +doc = ["sphinx (>=2,<4)", "sphinx-rtd-theme (>=0.1.6,<1)", "lowdown (>=0.2.0,<1)"] +test = ["pytest-runner (>=2.7,<3)", "pytest (>=2.3.5,<5)", "pytest-cov (>=2,<3)"] [[package]] name = "colorama" @@ -331,39 +330,30 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] -name = "cx-Freeze" +name = "cx-freeze" version = "6.11.1" description = "Create standalone executables from Python scripts" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\" and python_version < \"3.10\""} -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} -lief = {version = ">=0.11.5", markers = "sys_platform == \"win32\" and python_version <= \"3.10\""} -packaging = ">=21.0" -patchelf = {version = ">=0.12", markers = "sys_platform == \"linux\""} -setuptools = ">=59.0.1,<=60.10.0" - -[package.extras] -dev = ["bump2version (>=1.0.1)", "cibuildwheel (==2.6.1)", "pre-commit (>=2.17.0)", "pylint (>=2.13.0)"] -doc = ["sphinx (>=5.0.1,<6.0.0)", "sphinx-rtd-theme (==1.0.0)"] -test = ["nose (==1.3.7)", "pygments (>=2.11.2)", "pytest (>=7.0.1)", "pytest-cov (==3.0.0)", "pytest-mock (>=3.6.1)", "pytest-timeout (>=1.4.2)"] +cx-logging = {version = ">=3.0", markers = "sys_platform == \"win32\""} +importlib-metadata = ">=4.3.1" [[package]] name = "cx-logging" version = "3.0" description = "Python and C interfaces for logging" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -379,7 +369,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] [[package]] name = "dill" @@ -401,8 +391,8 @@ optional = false python-versions = ">=3.6,<4.0" [package.extras] -curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] dnssec = ["cryptography (>=2.6,<37.0)"] +curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.20)"] @@ -513,7 +503,7 @@ requests = ">=2.25.1,<=2.27.1" [package.extras] dev = ["wheel"] -test = ["black (==21.12b0)", "pre-commit (==2.17.0)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "pytest-cov (==2.12.1)", "requests-mock (==1.9.3)"] +test = ["pytest-cov (==2.12.1)", "requests-mock (==1.9.3)", "pytest (==4.6.11)", "pytest (==6.1.2)", "pytest (==6.2.5)", "black (==21.12b0)", "pre-commit (==2.17.0)"] [[package]] name = "gitdb" @@ -586,7 +576,7 @@ rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} six = ">=1.9.0" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"] +aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] pyopenssl = ["pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] @@ -658,9 +648,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -679,10 +669,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] -requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] name = "jedi" @@ -707,8 +697,8 @@ optional = false python-versions = ">=3.7" [package.extras] -test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] -trio = ["async_generator", "trio"] +trio = ["async-generator", "trio"] +test = ["async-timeout", "trio", "testpath", "pytest-asyncio (>=0.17)", "pytest-trio", "pytest"] [[package]] name = "jinja2" @@ -761,8 +751,8 @@ pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-enabler", "pytest-flake8", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "lazy-object-proxy" @@ -772,14 +762,6 @@ category = "dev" optional = false python-versions = ">=3.6" -[[package]] -name = "lief" -version = "0.12.1" -description = "Library to instrument executable formats" -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "log4mongo" version = "1.7.0" @@ -827,7 +809,7 @@ python-versions = "*" pyaaf2 = "1.4.0" [package.extras] -dev = ["check-manifest", "coverage (>=4.5)", "flake8 (>=3.5)", "urllib3 (>=1.24.3)"] +dev = ["check-manifest", "flake8 (>=3.5)", "coverage (>=4.5)", "urllib3 (>=1.24.3)"] view = ["PySide2 (>=5.11,<6.0)"] [package.source] @@ -839,7 +821,7 @@ reference = "openpype" name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -861,9 +843,9 @@ pynacl = ">=1.0.1" six = "*" [package.extras] -all = ["bcrypt (>=3.1.3)", "gssapi (>=1.4.1)", "invoke (>=1.3)", "pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "pywin32 (>=2.1.8)"] -ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"] -gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] +gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=1.3)"] [[package]] @@ -878,17 +860,6 @@ python-versions = ">=3.6" qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] -[[package]] -name = "patchelf" -version = "0.15.0.0" -description = "A small utility to modify the dynamic linker and RPATH of ELF executables." -category = "main" -optional = false -python-versions = "*" - -[package.extras] -test = ["importlib-metadata", "pytest"] - [[package]] name = "pathlib2" version = "2.3.7.post1" @@ -921,8 +892,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pluggy" @@ -1083,7 +1054,7 @@ python-versions = "*" aws = ["pymongo-auth-aws (<2.0.0)"] encryption = ["pymongocrypt (>=1.1.0,<2.0.0)"] gssapi = ["pykerberos"] -ocsp = ["certifi", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +ocsp = ["pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)", "certifi"] snappy = ["python-snappy"] srv = ["dnspython (>=1.16.0,<1.17.0)"] tls = ["ipaddress"] @@ -1101,8 +1072,8 @@ python-versions = ">=3.6" cffi = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] [[package]] name = "pynput" @@ -1217,7 +1188,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-print" @@ -1410,19 +1381,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "setuptools" -version = "60.10.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-inline-tabs", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "shotgun-api3" version = "3.3.3" @@ -1455,8 +1413,8 @@ optional = false python-versions = ">=3.6.0" [package.extras] -optional = ["SQLAlchemy (>=1,<2)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=10,<11)"] -testing = ["Flask (>=1,<2)", "Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (<2)", "black (==22.3.0)", "boto3 (<=2)", "click (==8.0.4)", "codecov (>=2,<3)", "databases (>=0.5)", "flake8 (>=4,<5)", "itsdangerous (==1.1.0)", "moto (>=3,<4)", "psutil (>=5,<6)", "pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "pytest-cov (>=2,<3)"] +optional = ["aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "SQLAlchemy (>=1,<2)", "websockets (>=10,<11)", "websocket-client (>=1,<2)"] +testing = ["pytest (>=6.2.5,<7)", "pytest-asyncio (<1)", "Flask-Sockets (>=0.2,<1)", "Flask (>=1,<2)", "Werkzeug (<2)", "itsdangerous (==1.1.0)", "Jinja2 (==3.0.3)", "pytest-cov (>=2,<3)", "codecov (>=2,<3)", "flake8 (>=4,<5)", "black (==22.3.0)", "click (==8.0.4)", "psutil (>=5,<6)", "databases (>=0.5)", "boto3 (<=2)", "moto (>=3,<4)"] [[package]] name = "smmap" @@ -1511,8 +1469,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest (>=4.6)", "typed-ast"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] +test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-qt-documentation" @@ -1527,9 +1485,9 @@ docutils = "*" sphinx = "*" [package.extras] +test = ["pytest-cov", "pytest (>=3.0.0)"] +lint = ["pylint", "flake8", "black"] dev = ["pre-commit"] -lint = ["black", "flake8", "pylint"] -test = ["pytest (>=3.0.0)", "pytest-cov"] [[package]] name = "sphinx-rtd-theme" @@ -1544,7 +1502,7 @@ docutils = "<0.18" sphinx = ">=1.6" [package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] +dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] [[package]] name = "sphinxcontrib-applehelp" @@ -1555,7 +1513,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] @@ -1567,7 +1525,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] @@ -1579,8 +1537,8 @@ optional = false python-versions = ">=3.6" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest", "html5lib"] [[package]] name = "sphinxcontrib-jsmath" @@ -1591,7 +1549,7 @@ optional = false python-versions = ">=3.5" [package.extras] -test = ["flake8", "mypy", "pytest"] +test = ["pytest", "flake8", "mypy"] [[package]] name = "sphinxcontrib-qthelp" @@ -1602,7 +1560,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] @@ -1614,7 +1572,7 @@ optional = false python-versions = ">=3.5" [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] @@ -1630,7 +1588,7 @@ sphinxcontrib-serializinghtml = "*" [package.extras] lint = ["flake8"] -test = ["Sphinx", "pytest", "sqlalchemy", "whoosh"] +test = ["pytest", "sqlalchemy", "whoosh", "sphinx"] [[package]] name = "stone" @@ -1701,8 +1659,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -1724,17 +1682,6 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] six = "*" -[[package]] -name = "wheel" -version = "0.37.1" -description = "A built-package format for Python" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.extras] -test = ["pytest (>=3.0.0)", "pytest-cov"] - [[package]] name = "wrapt" version = "1.14.1" @@ -1756,8 +1703,8 @@ aiohttp = "<4" yarl = "*" [package.extras] -develop = ["Sphinx", "async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov", "sphinxcontrib-plantuml", "tox (>=2.4)"] -testing = ["async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov"] +develop = ["async-timeout", "coverage (!=4.3)", "coveralls", "pytest", "pytest-aiohttp", "pytest-cov", "sphinx", "sphinxcontrib-plantuml", "tox (>=2.4)"] +testing = ["async-timeout", "pytest", "pytest-aiohttp", "pytest-cov", "coverage (!=4.3)", "coveralls"] ujson = ["ujson"] [[package]] @@ -1782,13 +1729,13 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "6a5f6910109c9ed6fb1cc1cb1cef7b21a0055a17c393175a1a7aabc00e35d54f" +content-hash = "de7422afb6aed02f75e1696afdda9ad6c7bf32da76b5022ee3e8f71a1ac4bae2" [metadata.files] acre = [] @@ -1870,10 +1817,7 @@ aiohttp-json-rpc = [ {file = "aiohttp-json-rpc-0.13.3.tar.gz", hash = "sha256:6237a104478c22c6ef96c7227a01d6832597b414e4b79a52d85593356a169e99"}, {file = "aiohttp_json_rpc-0.13.3-py3-none-any.whl", hash = "sha256:4fbd197aced61bd2df7ae3237ead7d3e08833c2ccf48b8581e1828c95ebee680"}, ] -aiohttp-middlewares = [ - {file = "aiohttp-middlewares-2.1.0.tar.gz", hash = "sha256:5863970d944dc63faedc96ef324a7fe2bcefefebe29acc90cd641236322d00c3"}, - {file = "aiohttp_middlewares-2.1.0-py3-none-any.whl", hash = "sha256:c83d48702e6a8669981976f39a60e83d059dc01d7b1ee651aec5d4cb807ff784"}, -] +aiohttp-middlewares = [] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, @@ -1891,10 +1835,7 @@ arrow = [ {file = "arrow-0.17.0-py2.py3-none-any.whl", hash = "sha256:e098abbd9af3665aea81bdd6c869e93af4feb078e98468dd351c383af187aac5"}, {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] -astroid = [ - {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"}, - {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"}, -] +astroid = [] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1903,112 +1844,21 @@ asynctest = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, ] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] +atomicwrites = [] +attrs = [] autopep8 = [ {file = "autopep8-1.5.7-py2.py3-none-any.whl", hash = "sha256:aa213493c30dcdac99537249ee65b24af0b2c29f2e83cd8b3f68760441ed0db9"}, {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] -babel = [ - {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, - {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, -] -bcrypt = [ - {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521"}, - {file = "bcrypt-3.2.2-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40"}, - {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa"}, - {file = "bcrypt-3.2.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa"}, - {file = "bcrypt-3.2.2-cp36-abi3-win32.whl", hash = "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e"}, - {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, - {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, -] +babel = [] +bcrypt = [] blessed = [ {file = "blessed-1.19.1-py2.py3-none-any.whl", hash = "sha256:63b8554ae2e0e7f43749b6715c734cc8f3883010a809bf16790102563e6cf25b"}, {file = "blessed-1.19.1.tar.gz", hash = "sha256:9a0d099695bf621d4680dd6c73f6ad547f6a3442fbdbe80c4b1daa1edbc492fc"}, ] -cachetools = [ - {file = "cachetools-5.2.0-py3-none-any.whl", hash = "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"}, - {file = "cachetools-5.2.0.tar.gz", hash = "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757"}, -] -certifi = [ - {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, - {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, -] -cffi = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] +cachetools = [] +certifi = [] +cffi = [] charset-normalizer = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, @@ -2021,10 +1871,7 @@ clique = [ {file = "clique-1.6.1-py2.py3-none-any.whl", hash = "sha256:8619774fa035661928dd8c93cd805acf2d42533ccea1b536c09815ed426c9858"}, {file = "clique-1.6.1.tar.gz", hash = "sha256:90165c1cf162d4dd1baef83ceaa1afc886b453e379094fa5b60ea470d1733e66"}, ] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] +colorama = [] commonmark = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, @@ -2033,99 +1880,20 @@ coolname = [ {file = "coolname-1.1.0-py2.py3-none-any.whl", hash = "sha256:e6a83a0ac88640f4f3d2070438dbe112fe80cfebc119c93bd402976ec84c0978"}, {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, ] -coverage = [ - {file = "coverage-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f50d3a822947572496ea922ee7825becd8e3ae6fbd2400cd8236b7d64b17f285"}, - {file = "coverage-6.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5191d53afbe5b6059895fa7f58223d3751c42b8101fb3ce767e1a0b1a1d8f87"}, - {file = "coverage-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04010af3c06ce2bfeb3b1e4e05d136f88d88c25f76cd4faff5d1fd84d11581ea"}, - {file = "coverage-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6630d8d943644ea62132789940ca97d05fac83f73186eaf0930ffa715fbdab6b"}, - {file = "coverage-6.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05de0762c1caed4a162b3e305f36cf20a548ff4da0be6766ad5c870704be3660"}, - {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e3a41aad5919613483aad9ebd53336905cab1bd6788afd3995c2a972d89d795"}, - {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a2738ba1ee544d6f294278cfb6de2dc1f9a737a780469b5366e662a218f806c3"}, - {file = "coverage-6.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a0d2df4227f645a879010461df2cea6b7e3fb5a97d7eafa210f7fb60345af9e8"}, - {file = "coverage-6.4.3-cp310-cp310-win32.whl", hash = "sha256:73a10939dc345460ca0655356a470dd3de9759919186a82383c87b6eb315faf2"}, - {file = "coverage-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:53c8edd3b83a4ddba3d8c506f1359401e7770b30f2188f15c17a338adf5a14db"}, - {file = "coverage-6.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1eda5cae434282712e40b42aaf590b773382afc3642786ac3ed39053973f61f"}, - {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59fc88bc13e30f25167e807b8cad3c41b7218ef4473a20c86fd98a7968733083"}, - {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75314b00825d70e1e34b07396e23f47ed1d4feedc0122748f9f6bd31a544840"}, - {file = "coverage-6.4.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52f8b9fcf3c5e427d51bbab1fb92b575a9a9235d516f175b24712bcd4b5be917"}, - {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5a559aab40c716de80c7212295d0dc96bc1b6c719371c20dd18c5187c3155518"}, - {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:306788fd019bb90e9cbb83d3f3c6becad1c048dd432af24f8320cf38ac085684"}, - {file = "coverage-6.4.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:920a734fe3d311ca01883b4a19aa386c97b82b69fbc023458899cff0a0d621b9"}, - {file = "coverage-6.4.3-cp37-cp37m-win32.whl", hash = "sha256:ab9ef0187d6c62b09dec83a84a3b94f71f9690784c84fd762fb3cf2d2b44c914"}, - {file = "coverage-6.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:39ebd8e120cb77a06ee3d5fc26f9732670d1c397d7cd3acf02f6f62693b89b80"}, - {file = "coverage-6.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc698580216050b5f4a34d2cdd2838b429c53314f1c4835fab7338200a8396f2"}, - {file = "coverage-6.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:877ee5478fd78e100362aed56db47ccc5f23f6e7bb035a8896855f4c3e49bc9b"}, - {file = "coverage-6.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:555a498999c44f5287cc95500486cd0d4f021af9162982cbe504d4cb388f73b5"}, - {file = "coverage-6.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff095a5aac7011fdb51a2c82a8fae9ec5211577f4b764e1e59cfa27ceeb1b59"}, - {file = "coverage-6.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de1e9335e2569974e20df0ce31493d315a830d7987e71a24a2a335a8d8459d3"}, - {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7856ea39059d75f822ff0df3a51ea6d76307c897048bdec3aad1377e4e9dca20"}, - {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:411fdd9f4203afd93b056c0868c8f9e5e16813e765de962f27e4e5798356a052"}, - {file = "coverage-6.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cdf7b83f04a313a21afb1f8730fe4dd09577fefc53bbdfececf78b2006f4268e"}, - {file = "coverage-6.4.3-cp38-cp38-win32.whl", hash = "sha256:ab2b1a89d2bc7647622e9eaf06128a5b5451dccf7c242deaa31420b055716481"}, - {file = "coverage-6.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:0e34247274bde982bbc613894d33f9e36358179db2ed231dd101c48dd298e7b0"}, - {file = "coverage-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b104b6b1827d6a22483c469e3983a204bcf9c6bf7544bf90362c4654ebc2edf3"}, - {file = "coverage-6.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adf1a0d272633b21d645dd6e02e3293429c1141c7d65a58e4cbcd592d53b8e01"}, - {file = "coverage-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff9832434a9193fbd716fbe05f9276484e18d26cc4cf850853594bb322807ac3"}, - {file = "coverage-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:923f9084d7e1d31b5f74c92396b05b18921ed01ee5350402b561a79dce3ea48d"}, - {file = "coverage-6.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d64304acf79766e650f7acb81d263a3ea6e2d0d04c5172b7189180ff2c023c"}, - {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fc294de50941d3da66a09dca06e206297709332050973eca17040278cb0918ff"}, - {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a42eaaae772f14a5194f181740a67bfd48e8806394b8c67aa4399e09d0d6b5db"}, - {file = "coverage-6.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4822327b35cb032ff16af3bec27f73985448f08e874146b5b101e0e558b613dd"}, - {file = "coverage-6.4.3-cp39-cp39-win32.whl", hash = "sha256:f217850ac0e046ede611312703423767ca032a7b952b5257efac963942c055de"}, - {file = "coverage-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0a84376e4fd13cebce2c0ef8c2f037929c8307fb94af1e5dbe50272a1c651b5d"}, - {file = "coverage-6.4.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:068d6f2a893af838291b8809c876973d885543411ea460f3e6886ac0ee941732"}, - {file = "coverage-6.4.3.tar.gz", hash = "sha256:ec2ae1f398e5aca655b7084392d23e80efb31f7a660d2eecf569fb9f79b3fb94"}, -] -cryptography = [ - {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, - {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, - {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, - {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, - {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, - {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, - {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, - {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, - {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, - {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, - {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, - {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, -] -cx-Freeze = [ - {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e372b9e72ac0e2207ee65a9d404e2669da1134dc37f5ace9a2a779099d3aa868"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd293382e1ad270dddf5a2707db5dbb8600a1e0b0c9b0da7af9d61326eb1b325"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:feec2f36bce042da6a0d92690bc592b0dcec29218adc2278535cd13b28ec3485"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5aafcc6337856d5921b20f41acdcc8d0fe770388f3a072eb25163f8825f6c5d"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-win32.whl", hash = "sha256:b99cc0b6d6c1ba51bd9fe11dbfae9aabcf089ba779ea86d83d280e2e40f484e7"}, - {file = "cx_Freeze-6.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:f0dfe6acf25eb096faba7d4b4b001bcd0f818e372ea1f05d900665b0ad82b0b9"}, - {file = "cx_Freeze-6.11.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3a68e70dcb27b0720b131a35c5fdd096012fe00119a8e51d935f3fb3cd251c39"}, - {file = "cx_Freeze-6.11.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7bde925042d8843af9b6242a1bf3865dbbae088f3183a89a575124ec2e14a4"}, - {file = "cx_Freeze-6.11.1-cp36-cp36m-win32.whl", hash = "sha256:7698fb82b6f84b3426774b5f3bee770601f26b612306319664a02f1ec5160861"}, - {file = "cx_Freeze-6.11.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9848c975401b21a98aa896baabfed067c3e981afd5b5b0a8a5eabe5c9f23d3c5"}, - {file = "cx_Freeze-6.11.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87dcf5ceb78dc6af910c45238128fda2394b7c430d3fa469e87e1efdeeb5d4cc"}, - {file = "cx_Freeze-6.11.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb74d8cc1f8c658986acc19ea6875b985a979421f9bb9c310b43cd2ff5d90c44"}, - {file = "cx_Freeze-6.11.1-cp37-cp37m-win32.whl", hash = "sha256:971c0a8356ef0ee09a3097f9c9d5b52cde6d08d1ef80e997eb4a6e22fe0eff2f"}, - {file = "cx_Freeze-6.11.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7c1cb44379b2093cbdde77e302a376f29aa61999c73be6e8a559463db84b85c4"}, - {file = "cx_Freeze-6.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc90d6dbde66e8ddfe6b26f63fb2ea7d6d0e4568205f40660a63b8b200dcabcf"}, - {file = "cx_Freeze-6.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f56f618a23d86bdcfff22b29ec993117effd32a401060013105517301c0bf32"}, - {file = "cx_Freeze-6.11.1-cp38-cp38-win32.whl", hash = "sha256:4edfb5d65afb11eb9f0326d40d15445366481585705b3096f2cd090e30a36247"}, - {file = "cx_Freeze-6.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:cfb5a8032bf424c04814c9426425fa1db4cf8c280da948969eead9f616c0fd92"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0a3e32980269cfabc2e814978bfdf4382fe3cbc9ac64f9f1bdb1cd2ddf3a40d0"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:798bb7ca037c3c885efd3eda6756c84c7927c712b730b22a7f256440faa36d38"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5aa1759098ca4853200a79138b626a9caa2ccf829d662b28c82ec7e71ea97cde"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7962680ae04ee3afda1012026b5394a534e2526b68681d591158b7d8bc733bcf"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-win32.whl", hash = "sha256:da4f82fe27e71571c0ab9d700b5e6c6c631ae39133d9b6d7157939f1e9f37312"}, - {file = "cx_Freeze-6.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:aaf399b6ed5d54b7271980ae354605620bedcd52d722f57ad527bd989c56a875"}, - {file = "cx_Freeze-6.11.1.tar.gz", hash = "sha256:8f3a30c9e3394f290655e346d3b460910656b30ac6347a87499bb5ad365c6e7c"}, +coverage = [] +cryptography = [] +cx-freeze = [ + {file = "cx_Freeze-6.9-cp310-cp310-win32.whl", hash = "sha256:776d4fb68a4831691acbd3c374362b9b48ce2e568514a73c3d4cb14d5dcf1470"}, + {file = "cx_Freeze-6.9-cp310-cp310-win_amd64.whl", hash = "sha256:243f36d35a034a409cd6247d8cb5d1fbfd7374e3e668e813d0811f64d6bd5ed3"}, + {file = "cx_Freeze-6.9-cp36-cp36m-win32.whl", hash = "sha256:ffc855eabc735b693e2d604d71dce6d52d78a6ba1070c55d51e786dd68ed232c"}, + {file = "cx_Freeze-6.9-cp36-cp36m-win_amd64.whl", hash = "sha256:fe4e32a0c75b2b54491882926bf3ba12f8a3d589822a68a8be7c09f1dcca5546"}, + {file = "cx_Freeze-6.9-cp37-cp37m-win32.whl", hash = "sha256:99c292e7a31cb343efc0cf47f82220a44a4a3b8776651624cd8ee03c23104940"}, + {file = "cx_Freeze-6.9-cp37-cp37m-win_amd64.whl", hash = "sha256:738ab22f3a3f6bc220b16dccf2aa0603c3cd271b2a7a9d9480dab82311308b23"}, + {file = "cx_Freeze-6.9-cp38-cp38-win32.whl", hash = "sha256:c1c75df572858e623d0aa39771cd984c0abd8aacb43b2aca2d12d0bc95f25566"}, + {file = "cx_Freeze-6.9-cp38-cp38-win_amd64.whl", hash = "sha256:0788c895c47fdcf375151ce78ff42336c01aca7bc43daecb8f8f8356cdc42b43"}, + {file = "cx_Freeze-6.9-cp39-cp39-win32.whl", hash = "sha256:a31f5ddbc80b29e297370d868791470b0e3e9062db45038c23293a76ed039018"}, + {file = "cx_Freeze-6.9-cp39-cp39-win_amd64.whl", hash = "sha256:30708f603076713c0a839cdfb34f4126d68e9d61afb3d9a59daa9cf252033872"}, + {file = "cx_Freeze-6.9.tar.gz", hash = "sha256:673aa3199af2ef87fc03a43a30e5d78b27ced2cedde925da89c55b5657da267b"}, ] cx-logging = [ {file = "cx_Logging-3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9fcd297e5c51470521c47eff0f86ba844aeca6be97e13c3e2114ebdf03fa3c96"}, @@ -2142,14 +1910,8 @@ cx-logging = [ {file = "cx_Logging-3.0-cp39-cp39-win_amd64.whl", hash = "sha256:302e9c4f65a936c288a4fa59a90e7e142d9ef994aa29676731acafdcccdbb3f5"}, {file = "cx_Logging-3.0.tar.gz", hash = "sha256:ba8a7465facf7b98d8f494030fb481a2e8aeee29dc191e10383bb54ed42bdb34"}, ] -deprecated = [ - {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, - {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, -] -dill = [ - {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, - {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, -] +deprecated = [] +dill = [] dnspython = [ {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, @@ -2158,93 +1920,22 @@ docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] -dropbox = [ - {file = "dropbox-11.33.0-py2-none-any.whl", hash = "sha256:3ee9024631b80f18938556d5e27cbdede26d6dc0b73aeaa90fc075ce96c950b1"}, - {file = "dropbox-11.33.0-py3-none-any.whl", hash = "sha256:1a0cbc22b0d1dae96e18b37e3520e5c289de7eb1303935db40e4dbfc9bb9e59b"}, - {file = "dropbox-11.33.0.tar.gz", hash = "sha256:7c638b521169a460de38b9eaeb204fe918874f72d6c3eed005d064b6f37da9c1"}, -] +dropbox = [] enlighten = [ {file = "enlighten-1.10.2-py2.py3-none-any.whl", hash = "sha256:b237fe562b320bf9f1d4bb76d0c98e0daf914372a76ab87c35cd02f57aa9d8c1"}, {file = "enlighten-1.10.2.tar.gz", hash = "sha256:7a5b83cd0f4d095e59d80c648ebb5f7ffca0cd8bcf7ae6639828ee1ad000632a"}, ] -evdev = [ - {file = "evdev-1.6.0.tar.gz", hash = "sha256:ecfa01b5c84f7e8c6ced3367ac95288f43cd84efbfd7dd7d0cdbfc0d18c87a6a"}, -] +evdev = [] flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] -frozenlist = [ - {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"}, - {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"}, - {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"}, - {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"}, - {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"}, - {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"}, - {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"}, - {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"}, - {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"}, - {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"}, - {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"}, - {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"}, - {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"}, - {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"}, - {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"}, - {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"}, - {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"}, - {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"}, - {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"}, - {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"}, - {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"}, - {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"}, - {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"}, - {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"}, - {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"}, - {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"}, - {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, -] -ftrack-python-api = [ - {file = "ftrack-python-api-2.3.3.tar.gz", hash = "sha256:358f37e5b1c5635eab107c19e27a0c890d512877f78af35b1ac416e90c037295"}, - {file = "ftrack_python_api-2.3.3-py2.py3-none-any.whl", hash = "sha256:82834c4d5def5557a2ea547a7e6f6ba84d3129e8f90457d8bbd85b287a2c39f6"}, -] +frozenlist = [] +ftrack-python-api = [] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] -gazu = [ - {file = "gazu-0.8.30-py2.py3-none-any.whl", hash = "sha256:d692927a11314151bc33e7d67edee634053f70a3b09e4500dfc6626bfea18753"}, -] +gazu = [] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, @@ -2253,42 +1944,24 @@ gitpython = [ {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, ] -google-api-core = [ - {file = "google-api-core-2.8.2.tar.gz", hash = "sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc"}, - {file = "google_api_core-2.8.2-py3-none-any.whl", hash = "sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50"}, -] +google-api-core = [] google-api-python-client = [ {file = "google-api-python-client-1.12.11.tar.gz", hash = "sha256:1b4bd42a46321e13c0542a9e4d96fa05d73626f07b39f83a73a947d70ca706a9"}, {file = "google_api_python_client-1.12.11-py2.py3-none-any.whl", hash = "sha256:7e0a1a265c8d3088ee1987778c72683fcb376e32bada8d7767162bd9c503fd9b"}, ] -google-auth = [ - {file = "google-auth-2.10.0.tar.gz", hash = "sha256:7904dbd44b745c7323fef29565adee2fe7ff48473e2d94443aced40b0404a395"}, - {file = "google_auth-2.10.0-py2.py3-none-any.whl", hash = "sha256:1deba4a54f95ef67b4139eaf5c20eaa7047215eec9f6a2344599b8596db8863b"}, -] +google-auth = [] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, {file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"}, ] -googleapis-common-protos = [ - {file = "googleapis-common-protos-1.56.4.tar.gz", hash = "sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417"}, - {file = "googleapis_common_protos-1.56.4-py2.py3-none-any.whl", hash = "sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394"}, -] +googleapis-common-protos = [] httplib2 = [ {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, ] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -imagesize = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, - {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, -] +idna = [] +imagesize = [] +importlib-metadata = [] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -2301,18 +1974,12 @@ jedi = [ {file = "jedi-0.13.3-py2.py3-none-any.whl", hash = "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"}, {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] -jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, -] +jeepney = [] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] -jinxed = [ - {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"}, - {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"}, -] +jinxed = [] jsonschema = [ {file = "jsonschema-2.6.0-py2.py3-none-any.whl", hash = "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08"}, {file = "jsonschema-2.6.0.tar.gz", hash = "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"}, @@ -2360,58 +2027,16 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, ] -lief = [ - {file = "lief-0.12.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:4fbbc9d520de87ac22210c62d22a9b088e5460f9a028741311e6f68ef8877ddd"}, - {file = "lief-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443e4494df448ea1a021976258c7a6aca27d81b0612783fa3a84fab196fb9fcb"}, - {file = "lief-0.12.1-cp310-cp310-win32.whl", hash = "sha256:1c4019dddf03a5185462fb5ea04327cee08d40f46777b02f0773c7dc294552ea"}, - {file = "lief-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d7e09968f99ddf1e3983d3bcc16c62d1b6635a345fee8d8139f82b31bad457d6"}, - {file = "lief-0.12.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9fa6269ec4fa3f874b807fbba3c48a46af30df2497723f6966080e3eb630cb26"}, - {file = "lief-0.12.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b05cac5fa491e01e1819573bbbbcaea0a4229f4aa3a2edb231b5695ddaf2d"}, - {file = "lief-0.12.1-cp36-cp36m-win32.whl", hash = "sha256:f1292bff96579c18e01e20b7a14043052379fe6e9a476c1d6d88aca43e5f9ac7"}, - {file = "lief-0.12.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dab63876113bd573d64ce043f50153f6e2810e5e78256397aa0fe1fedf82ab84"}, - {file = "lief-0.12.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:5771f5226b62c885a7aa30c1b98040d39229a1dab889d03155e5538e57d0054b"}, - {file = "lief-0.12.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8ec307a762505076a6d31566225a231c44ec7063c0e7d751ac4654c674454c47"}, - {file = "lief-0.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a755f6088d3b2041e4402adf917ac87e5ad9d1c5278973f48a29a5631fe393eb"}, - {file = "lief-0.12.1-cp37-cp37m-win32.whl", hash = "sha256:5d746f7eb6d3bf35a0230c7184aaaf434cb1ea89d7e7c8e8fe14a49cf2bb17a0"}, - {file = "lief-0.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2d3ab7212da696bcbe5ca9dd78ceaa32dfb8a0e85e18001793b4441ef4624561"}, - {file = "lief-0.12.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:4360b0acd525ba77777cc38f0e5128c90c93cc4e91ab566ef3aa45b7f8a8c57e"}, - {file = "lief-0.12.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5e82e466d36cbabb28cc1a787b554d2feae5ab55c39cab58ef64fb6513bad92a"}, - {file = "lief-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa0022a3bf70ef46335639e61b946cc2d9cf012d60e263c215e3e64b1ce38b4"}, - {file = "lief-0.12.1-cp38-cp38-win32.whl", hash = "sha256:d29f91d9f64f67d3ada5b7e0e48ab084d825fb4601d32d9fecdd2bdf23cdad23"}, - {file = "lief-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:7dea6b3f17d362f93165379c46dadb012c73b1f751c8ceac256e5f43842cd86d"}, - {file = "lief-0.12.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:44012da4c32c670a97bb8a055a4ff16168cfaa757d03986f319aa3329a43e343"}, - {file = "lief-0.12.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e1d23997b0a71d34e766ff183be07854c6f698fd3d6aa44bf30b6b7f4f77ef55"}, - {file = "lief-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b845eca79c772041efb38b50cfaf951e24bc047ec462450b7e54e75b7e2bee0d"}, - {file = "lief-0.12.1-cp39-cp39-win32.whl", hash = "sha256:0df84ac2df20b14db12e69442d39b0e8cd89428ba3b131995e0570bcd3725460"}, - {file = "lief-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:960a2da9f28c8d5dba753bb9ab77e26b3c6ff9b9658918be95650ceb8ee91e68"}, - {file = "lief-0.12.1.zip", hash = "sha256:4ff4ccfae2e1ee4ccba2b5556027dbb56282b8a973c5835c5b597e8b7b664416"}, -] log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -2420,27 +2045,14 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -2450,12 +2062,6 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -2525,109 +2131,22 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] -opentimelineio = [ - {file = "OpenTimelineIO-0.14.0.dev1-cp310-cp310-linux_x86_64.whl", hash = "sha256:112c27ad419a79c88cd4ebac96278ab8f446fda4c6e1a70c871f2b24b3d003ef"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e2416ca76805a07ecbcdda65e5a31ce447e04e2db7082d72582740cbd8a16d7"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8806dc240496b72e16a0fddacb0b2e825d19656d80689098e6c5bd6a805bc84"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp36-cp36m-win_amd64.whl", hash = "sha256:0aa54488ca50b53ac247610cef23fb63619dd1993016c0cd4069e54526d5905c"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-linux_x86_64.whl", hash = "sha256:8b11287eb733ad1c7fc53d4af3e3f926c396add6c3a3c1417b9c2b001f7ef4ba"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:2cba2ce567fc06f042365393dbe8e99dc4c1361999fb7ddc03d4b8b0d5ddb894"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:3d96da307c1969c309974a6734c7f3e39925236c845c8289f25d4d7d00be3f0c"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:968cb0c5e6e7d697037b1cd4f7707521995a32fc51664139ed15004b93ab8106"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dae64aa418193ca094854a55efa8bcc5a5c2855b3679509987e8b48610d31"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp37-cp37m-win_amd64.whl", hash = "sha256:85dfae42f5a992ef85d0015f33f999751d846a484ef907e8834407d545a7ee6a"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aaac346cb758de719f88ac175d3948409ded39c5eed0844068402f70f3e90b6"}, - {file = "OpenTimelineIO-0.14.0.dev1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a09f6dd199218cf69faf42a8b21f5be2cd01764e57dbcc8456b3ced564eb110f"}, - {file = "OpenTimelineIO-0.14.0.dev1.tar.gz", hash = "sha256:d29eafd5188c3ad6c7b6d2095bf69984d590c331b701d3bbc644d7abd5f08606"}, -] +opentimelineio = [] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -paramiko = [ - {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, - {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, -] +paramiko = [] parso = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] -patchelf = [ - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:08e5e30a9415a8628de47726fbf15bfcd89be35df51c8a0a12372aebd0c5b4f6"}, - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:4ce9d08119816bc4316c8ecc5f33da42384934fc0fc9cfbdded53a4930705466"}, - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_17_s390x.manylinux2014_s390x.musllinux_1_1_s390x.whl", hash = "sha256:ae19b0f91aabc9af2608a4ca0395533f1df9122e6abc11ef2c8db6e4db0f98c2"}, - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_5_i686.manylinux1_i686.musllinux_1_1_i686.whl", hash = "sha256:f3f87aee44d1d1b2209e38c4227b0316bb03538df68d20b3d96205aa87868d95"}, - {file = "patchelf-0.15.0.0-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:52e48c08110f2988a9761a5a383f7ae35b1e8e06a140e320d18386d3510697ed"}, - {file = "patchelf-0.15.0.0.tar.gz", hash = "sha256:0f8dcf0df0ba919ce37e8aef67a08bde5326897098451df94ab3a5eedc9e08d9"}, -] pathlib2 = [ {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] -pillow = [ - {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, - {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, - {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, - {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, - {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, - {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"}, - {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, - {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, - {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, - {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, - {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, - {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, - {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, - {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, - {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, - {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, - {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, - {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, - {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, - {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, - {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, - {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, - {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, - {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, -] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] +pillow = [] +platformdirs = [] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -2640,22 +2159,7 @@ prefixed = [ {file = "prefixed-0.3.2-py2.py3-none-any.whl", hash = "sha256:5e107306462d63f2f03c529dbf11b0026fdfec621a9a008ca639d71de22995c3"}, {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] -protobuf = [ - {file = "protobuf-4.21.5-cp310-abi3-win32.whl", hash = "sha256:5310cbe761e87f0c1decce019d23f2101521d4dfff46034f8a12a53546036ec7"}, - {file = "protobuf-4.21.5-cp310-abi3-win_amd64.whl", hash = "sha256:e5c5a2886ae48d22a9d32fbb9b6636a089af3cd26b706750258ce1ca96cc0116"}, - {file = "protobuf-4.21.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ee04f5823ed98bb9a8c3b1dc503c49515e0172650875c3f76e225b223793a1f2"}, - {file = "protobuf-4.21.5-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:b04484d6f42f48c57dd2737a72692f4c6987529cdd148fb5b8e5f616862a2e37"}, - {file = "protobuf-4.21.5-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e0b272217aad8971763960238c1a1e6a65d50ef7824e23300da97569a251c55"}, - {file = "protobuf-4.21.5-cp37-cp37m-win32.whl", hash = "sha256:5eb0724615e90075f1d763983e708e1cef08e66b1891d8b8b6c33bc3b2f1a02b"}, - {file = "protobuf-4.21.5-cp37-cp37m-win_amd64.whl", hash = "sha256:011c0f267e85f5d73750b6c25f0155d5db1e9443cd3590ab669a6221dd8fcdb0"}, - {file = "protobuf-4.21.5-cp38-cp38-win32.whl", hash = "sha256:7b6f22463e2d1053d03058b7b4ceca6e4ed4c14f8c286c32824df751137bf8e7"}, - {file = "protobuf-4.21.5-cp38-cp38-win_amd64.whl", hash = "sha256:b52e7a522911a40445a5f588bd5b5e584291bfc5545e09b7060685e4b2ff814f"}, - {file = "protobuf-4.21.5-cp39-cp39-win32.whl", hash = "sha256:a7faa62b183d6a928e3daffd06af843b4287d16ef6e40f331575ecd236a7974d"}, - {file = "protobuf-4.21.5-cp39-cp39-win_amd64.whl", hash = "sha256:5e0ce02418ef03d7657a420ae8fd6fec4995ac713a3cb09164e95f694dbcf085"}, - {file = "protobuf-4.21.5-py2.py3-none-any.whl", hash = "sha256:bf711b451212dc5b0fa45ae7dada07d8e71a4b0ff0bc8e4783ee145f47ac4f82"}, - {file = "protobuf-4.21.5-py3-none-any.whl", hash = "sha256:3ec6f5b37935406bb9df9b277e79f8ed81d697146e07ef2ba8a5a272fb24b2c9"}, - {file = "protobuf-4.21.5.tar.gz", hash = "sha256:eb1106e87e095628e96884a877a51cdb90087106ee693925ec0a300468a9be3a"}, -] +protobuf = [] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -2714,14 +2218,8 @@ pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] -pygments = [ - {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, - {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, -] -pylint = [ - {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, - {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, -] +pygments = [] +pylint = [] pymongo = [ {file = "pymongo-3.12.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f"}, {file = "pymongo-3.12.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:a055d29f1302892a9389a382bed10a3f77708bcf3e49bfb76f7712fa5f391cc6"}, @@ -2848,42 +2346,10 @@ pynput = [ {file = "pynput-1.7.6-py3.9.egg", hash = "sha256:264429fbe676e98e9050ad26a7017453bdd08768adb25cafb918347cf9f1eb4a"}, {file = "pynput-1.7.6.tar.gz", hash = "sha256:3a5726546da54116b687785d38b1db56997ce1d28e53e8d22fc656d8b92e533c"}, ] -pyobjc-core = [ - {file = "pyobjc-core-8.5.tar.gz", hash = "sha256:704c275439856c0d1287469f0d589a7d808d48b754a93d9ce5415d4eaf06d576"}, - {file = "pyobjc_core-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c234143b48334443f5adcf26e668945a6d47bc1fa6223e80918c6c735a029d9"}, - {file = "pyobjc_core-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1486ee533f0d76f666804ce89723ada4db56bfde55e56151ba512d3f849857f8"}, - {file = "pyobjc_core-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:412de06dfa728301c04b3e46fd7453320a8ae8b862e85236e547cd797a73b490"}, - {file = "pyobjc_core-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b3e09cccb1be574a82cc9f929ae27fc4283eccc75496cb5d51534caa6bb83a3"}, - {file = "pyobjc_core-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:eeafe21f879666ab7f57efcc6b007c9f5f8733d367b7e380c925203ed83f000d"}, - {file = "pyobjc_core-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c0071686976d7ea8c14690950e504a13cb22b4ebb2bc7b5ec47c1c1c0f6eff41"}, -] -pyobjc-framework-applicationservices = [ - {file = "pyobjc-framework-ApplicationServices-8.5.tar.gz", hash = "sha256:fa3015ef8e3add90af3447d7fdcc7f8dd083cc2a1d58f99a569480a2df10d2b1"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:436b16ebe448a829a8312e10208eec81a2adcae1fff674dbcc3262e1bd76e0ca"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:408958d14aa7fcf46f2163754c211078bc63be1368934d86188202914dce077d"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d6cd4ce192859a22e208da4d7177a1c3ceb1ef2f64c339fd881102b1210cadd"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0251d092adb1d2d116fd9f147ceef0e53b158a46c21245131c40b9d7b786d0db"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9742e69fe6d4545d0e02b0ad0a7a2432bc9944569ee07d6e90ffa5ef614df9f7"}, - {file = "pyobjc_framework_ApplicationServices-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16f5677c14ea903c6aaca1dd121521825c39e816cae696d6ae32c0b287252ab2"}, -] -pyobjc-framework-cocoa = [ - {file = "pyobjc-framework-Cocoa-8.5.tar.gz", hash = "sha256:569bd3a020f64b536fb2d1c085b37553e50558c9f907e08b73ffc16ae68e1861"}, - {file = "pyobjc_framework_Cocoa-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7a7c160416696bf6035dfcdf0e603aaa52858d6afcddfcc5ab41733619ac2529"}, - {file = "pyobjc_framework_Cocoa-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6ceba444282030be8596b812260e8d28b671254a51052ad778d32da6e17db847"}, - {file = "pyobjc_framework_Cocoa-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f46b2b161b8dd40c7b9e00bc69636c3e6480b2704a69aee22ee0154befbe163a"}, - {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b31d425aee8698cbf62b187338f5ca59427fa4dca2153a73866f7cb410713119"}, - {file = "pyobjc_framework_Cocoa-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:898359ac1f76eedec8aa156847682378a8950824421c40edb89391286e607dc4"}, - {file = "pyobjc_framework_Cocoa-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:baa2947f76b119a3360973d74d57d6dada87ac527bab9a88f31596af392f123c"}, -] -pyobjc-framework-quartz = [ - {file = "pyobjc-framework-Quartz-8.5.tar.gz", hash = "sha256:d2bc5467a792ddc04814f12a1e9c2fcaf699a1c3ad3d4264cfdce6b9c7b10624"}, - {file = "pyobjc_framework_Quartz-8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e9f0fb663f7872c9de94169031ac42b91ad01bd4cad49a9f1a0164be8f028426"}, - {file = "pyobjc_framework_Quartz-8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:567eec91287cfe9a1b6433717192c585935de8f3daa28d82ce72fdd6c7ac00f6"}, - {file = "pyobjc_framework_Quartz-8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f910ab41a712ffc7a8c3e3716a2d6f39ea4419004b26a2fd2d2f740ff5c262c"}, - {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29d07066781628278bf0e5278abcfc96ef6724c66c5629a0b4c214d319a82e55"}, - {file = "pyobjc_framework_Quartz-8.5-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:72abcde1a3d72be11f2c881c9b9872044c8f2de86d2047b67fe771713638b107"}, - {file = "pyobjc_framework_Quartz-8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8809b9a2df2f461697bdb45b6d1b5a4f881f88f09450e3990858e64e3e26c530"}, -] +pyobjc-core = [] +pyobjc-framework-applicationservices = [] +pyobjc-framework-cocoa = [] +pyobjc-framework-quartz = [] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, @@ -2907,14 +2373,8 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-engineio = [ - {file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"}, - {file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"}, -] -python-socketio = [ - {file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"}, - {file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"}, -] +python-engineio = [] +python-socketio = [] python-xlib = [ {file = "python-xlib-0.31.tar.gz", hash = "sha256:74d83a081f532bc07f6d7afcd6416ec38403d68f68b9b9dc9e1f28fbf2d799e9"}, {file = "python_xlib-0.31-py2.py3-none-any.whl", hash = "sha256:1ec6ce0de73d9e6592ead666779a5732b384e5b8fb1f1886bd0a81cafa477759"}, @@ -2922,10 +2382,7 @@ python-xlib = [ python3-xlib = [ {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, ] -pytz = [ - {file = "pytz-2022.2-py2.py3-none-any.whl", hash = "sha256:d9b245e63af49c4e51afdec5402f56b99c0cb483a84a12bb8b7db980386baade"}, - {file = "pytz-2022.2.tar.gz", hash = "sha256:bc824559e43e8ab983426a49525079d186b25372ff63aa3430ccd527d95edc3a"}, -] +pytz = [] pywin32 = [ {file = "pywin32-301-cp35-cp35m-win32.whl", hash = "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7"}, {file = "pywin32-301-cp35-cp35m-win_amd64.whl", hash = "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72"}, @@ -2942,10 +2399,7 @@ pywin32-ctypes = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] -"qt.py" = [ - {file = "Qt.py-1.3.7-py2.py3-none-any.whl", hash = "sha256:150099d1c6f64c9621a2c9d79d45102ec781c30ee30ee69fc082c6e9be7324fe"}, - {file = "Qt.py-1.3.7.tar.gz", hash = "sha256:803c7bdf4d6230f9a466be19d55934a173eabb61406d21cb91e80c2a3f773b1f"}, -] +"qt.py" = [] qtawesome = [ {file = "QtAwesome-0.7.3-py2.py3-none-any.whl", hash = "sha256:ddf4530b4af71cec13b24b88a4cdb56ec85b1e44c43c42d0698804c7137b09b0"}, {file = "QtAwesome-0.7.3.tar.gz", hash = "sha256:b98b9038d19190e83ab26d91c4d8fc3a36591ee2bc7f5016d4438b8240d097bd"}, @@ -2958,35 +2412,19 @@ recommonmark = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, ] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] -rsa = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] -secretstorage = [ - {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, - {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, -] +requests = [] +rsa = [] +secretstorage = [] semver = [ {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, ] -setuptools = [ - {file = "setuptools-60.10.0-py3-none-any.whl", hash = "sha256:782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96"}, - {file = "setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"}, -] shotgun-api3 = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -slack-sdk = [ - {file = "slack_sdk-3.18.1-py2.py3-none-any.whl", hash = "sha256:63ce5e6253a31873d6c921c9feaa842a93a2f56e6e009cb7daf406f4bc4df798"}, - {file = "slack_sdk-3.18.1.tar.gz", hash = "sha256:a25d3d2bf0bf605d54d764d4a463fe7c0659ee24c13d75653e2bec247bd5998b"}, -] +slack-sdk = [] smmap = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, @@ -2995,18 +2433,9 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] -speedcopy = [ - {file = "speedcopy-2.1.4-py3-none-any.whl", hash = "sha256:e09eb1de67ae0e0b51d5b99a28882009d565a37a3cb3c6bae121e3a5d3cccb17"}, - {file = "speedcopy-2.1.4.tar.gz", hash = "sha256:eff007a97e49ec1934df4fa8074f4bd1cf4a3b14c5499d914988785cff0c199a"}, -] -sphinx = [ - {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, - {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, -] -sphinx-qt-documentation = [ - {file = "sphinx_qt_documentation-0.4-py3-none-any.whl", hash = "sha256:fa131093f75cd1bd48699cd132e18e4d46ba9eaadc070e6026867cea75ecdb7b"}, - {file = "sphinx_qt_documentation-0.4.tar.gz", hash = "sha256:f43ba17baa93e353fb94045027fb67f9d935ed158ce8662de93f08b88eec6774"}, -] +speedcopy = [] +sphinx = [] +sphinx-qt-documentation = [] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, @@ -3055,44 +2484,13 @@ tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] -typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, -] +typed-ast = [] +typing-extensions = [] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] -urllib3 = [ - {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, - {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, -] +urllib3 = [] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, @@ -3101,142 +2499,10 @@ websocket-client = [ {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] -wheel = [ - {file = "wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a"}, - {file = "wheel-0.37.1.tar.gz", hash = "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4"}, -] -wrapt = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, -] +wrapt = [] wsrpc-aiohttp = [ {file = "wsrpc-aiohttp-3.2.0.tar.gz", hash = "sha256:f467abc51bcdc760fc5aeb7041abdeef46eeca3928dc43dd6e7fa7a533563818"}, {file = "wsrpc_aiohttp-3.2.0-py3-none-any.whl", hash = "sha256:fa9b0bf5cb056898cb5c9f64cbc5eacb8a5dd18ab1b7f0cd4a2208b4a7fde282"}, ] -yarl = [ - {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, - {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, - {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, - {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, - {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, - {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, - {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, - {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, - {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, - {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, - {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, - {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, - {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, - {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, - {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, - {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, - {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, - {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, - {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, - {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, - {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, - {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, - {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, -] -zipp = [ - {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, - {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, -] +yarl = [] +zipp = [] diff --git a/pyproject.toml b/pyproject.toml index 2a0606a10c..0deb4f465b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,13 +70,12 @@ requests = "^2.25.1" pysftp = "^0.2.9" dropbox = "^11.20.0" aiohttp-middlewares = "^2.0.0" -cx-Freeze = "6.11.1" [tool.poetry.dev-dependencies] flake8 = "^3.7" autopep8 = "^1.4" coverage = "*" -cx_freeze = "^6.11.1" +cx_freeze = "~6.9" GitPython = "^3.1.17" jedi = "^0.13" Jinja2 = "^2.11" From 5079d05df103adf2690b161d1e726ebdb34038d9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 15:50:30 +0200 Subject: [PATCH 193/513] Hardcoded downgraded version of Poetry --- tools/create_env.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index 3f956e5c6a..cdb97d4942 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -68,6 +68,7 @@ function Install-Poetry() { } $env:POETRY_HOME="$openpype_root\.poetry" + $env:POETRY_VERSION="1.1.15" (Invoke-WebRequest -Uri https://install.python-poetry.org/ -UseBasicParsing).Content | & $($python) - } From 73309ffc324544726ecdbe5e43749d304637a91d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 15:51:44 +0200 Subject: [PATCH 194/513] Revert "Fix - update cx-freeze" This reverts commit 4fc90655d7e5b6869650638e4b7ff064ebc2271e. --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 726b248f8c..21b6bda880 100644 --- a/poetry.lock +++ b/poetry.lock @@ -339,7 +339,7 @@ test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", [[package]] name = "cx-freeze" -version = "6.11.1" +version = "6.9" description = "Create standalone executables from Python scripts" category = "dev" optional = false From d2240544ad2c00558114ef4122618ca2e73ad0e6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Sep 2022 15:55:58 +0200 Subject: [PATCH 195/513] Hardcoded downgraded version of Poetry Latest 1.2 version breaks build because cx-freeze. Latest cx-freeze works on Win, fails on Linux. --- tools/create_env.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/create_env.sh b/tools/create_env.sh index fba3942b87..1ecd960fe1 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -109,6 +109,7 @@ detect_python () { install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." export POETRY_HOME="$openpype_root/.poetry" + export POETRY_VERSION="1.1.15" command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } curl -sSL https://install.python-poetry.org/ | python - } From f0dc54f78e230c1d39c0c33b9360b97876d6ce22 Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 16:32:45 +0200 Subject: [PATCH 196/513] Update credentials.py Implementation got moved from emit_on_kitsu_login to the login function --- openpype/modules/kitsu/utils/credentials.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index d853d5b437..013a9966c5 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -33,7 +33,10 @@ def validate_credentials( except gazu.exception.AuthFailedException: return False - emit_on_kitsu_login(login) + emit_event("kitsu.user.logged", + data={"username": login}, + source="kitsu") + return True @@ -103,15 +106,4 @@ def set_credentials_envs(login: str, password: str): password (str): Kitsu user password """ os.environ["KITSU_LOGIN"] = login - os.environ["KITSU_PWD"] = password - - -def emit_on_kitsu_login(login: str): - """Notifies listeners that Kitsu module succesfully connected, - and passes them data - - Args: - login (str): Kitsu username - """ - event_data = {"username": login} - emit_event("kitsu.user.logged", data=event_data, source="kitsu") + os.environ["KITSU_PWD"] = password \ No newline at end of file From 58fd5a1b097bd7d7e71f004b3fff8529296b1102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Fri, 2 Sep 2022 16:32:52 +0200 Subject: [PATCH 197/513] Make to Optional Arguments --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index f013251bb1..199c59053b 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -318,7 +318,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects(login: str, password: str, ignore_projects: list = []): +def sync_all_projects(login: str, password: str, ignore_projects=[]): """Update all OP projects in DB with Zou data. Args: From a72b84a58695b11b6128a5985014c7a0ebea15dd Mon Sep 17 00:00:00 2001 From: Hayley GUILLOT Date: Fri, 2 Sep 2022 16:33:34 +0200 Subject: [PATCH 198/513] Forgot to lint --- openpype/modules/kitsu/utils/credentials.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index 013a9966c5..adcfb07cd5 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -33,10 +33,8 @@ def validate_credentials( except gazu.exception.AuthFailedException: return False - emit_event("kitsu.user.logged", - data={"username": login}, - source="kitsu") - + emit_event("kitsu.user.logged", data={"username": login}, source="kitsu") + return True @@ -106,4 +104,4 @@ def set_credentials_envs(login: str, password: str): password (str): Kitsu user password """ os.environ["KITSU_LOGIN"] = login - os.environ["KITSU_PWD"] = password \ No newline at end of file + os.environ["KITSU_PWD"] = password From 88e4798b535c47cefce3dc2a1ed9aacf60dd0f68 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 2 Sep 2022 19:50:56 +0200 Subject: [PATCH 199/513] Remove old type hint --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 - 1 file changed, 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 bb48fe6902..68d55fef5d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -641,7 +641,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): return result def _patch_workfile(self): - # type: (str, dict) -> [str, None] """Patch Maya scene. This will take list of patches (lines to add) and apply them to From 153f2c2e95526f0ace683249486316cfd5bc213e Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 3 Sep 2022 04:15:14 +0000 Subject: [PATCH 200/513] [Automated] Bump version --- CHANGELOG.md | 46 ++++++++++++++++++++++----------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c8834dd49..b35e89e96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,42 @@ # Changelog -## [3.14.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.2-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) **🆕 New features** +- Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) - Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) +**🚀 Enhancements** + +- SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) +- Maya: move set render settings menu entry [\#3669](https://github.com/pypeclub/OpenPype/pull/3669) +- Scene Inventory: Maya add actions to select from or to scene [\#3659](https://github.com/pypeclub/OpenPype/pull/3659) + **🐛 Bug fixes** +- Fix - changed format of version string in pyproject.toml [\#3777](https://github.com/pypeclub/OpenPype/pull/3777) - Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) +- Fix version resolution [\#3757](https://github.com/pypeclub/OpenPype/pull/3757) +- Maya: `containerise` dont skip empty values [\#3674](https://github.com/pypeclub/OpenPype/pull/3674) **🔀 Refactored code** +- AfterEffects: Use new Extractor location [\#3784](https://github.com/pypeclub/OpenPype/pull/3784) +- General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) +- General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) +- General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) -- Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) - Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) +- Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) + +**Merged pull requests:** + +- Standalone Publisher: Ignore empty labels, then still use name like other asset models [\#3779](https://github.com/pypeclub/OpenPype/pull/3779) ## [3.14.1](https://github.com/pypeclub/OpenPype/tree/3.14.1) (2022-08-30) @@ -63,6 +82,7 @@ - General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) +- Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) @@ -93,6 +113,7 @@ **🚀 Enhancements** - Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) +- Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) - Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) - Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) - General: Optimized OCIO configs [\#3650](https://github.com/pypeclub/OpenPype/pull/3650) @@ -103,43 +124,22 @@ - General: Fix finding of last version [\#3656](https://github.com/pypeclub/OpenPype/pull/3656) - General: Extract Review can scale with pixel aspect ratio [\#3644](https://github.com/pypeclub/OpenPype/pull/3644) - Maya: Refactor moved usage of CreateRender settings [\#3643](https://github.com/pypeclub/OpenPype/pull/3643) -- General: Hero version representations have full context [\#3638](https://github.com/pypeclub/OpenPype/pull/3638) -- Nuke: color settings for render write node is working now [\#3632](https://github.com/pypeclub/OpenPype/pull/3632) -- Maya: FBX support for update in reference loader [\#3631](https://github.com/pypeclub/OpenPype/pull/3631) **🔀 Refactored code** - General: Use client projects getter [\#3673](https://github.com/pypeclub/OpenPype/pull/3673) - Resolve: Match folder structure to other hosts [\#3653](https://github.com/pypeclub/OpenPype/pull/3653) - Maya: Hosts as modules [\#3647](https://github.com/pypeclub/OpenPype/pull/3647) -- TimersManager: Plugins are in timers manager module [\#3639](https://github.com/pypeclub/OpenPype/pull/3639) -- General: Move workfiles functions into pipeline [\#3637](https://github.com/pypeclub/OpenPype/pull/3637) **Merged pull requests:** - Deadline: Global job pre load is not Pype 2 compatible [\#3666](https://github.com/pypeclub/OpenPype/pull/3666) - Maya: Remove unused get current renderer logic [\#3645](https://github.com/pypeclub/OpenPype/pull/3645) -- Kitsu|Fix: Movie project type fails & first loop children names [\#3636](https://github.com/pypeclub/OpenPype/pull/3636) -- fix the bug of failing to extract look when UDIMs format used in AiImage [\#3628](https://github.com/pypeclub/OpenPype/pull/3628) ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.13.0-nightly.1...3.13.0) -**🚀 Enhancements** - -- Editorial: Mix audio use side file for ffmpeg filters [\#3630](https://github.com/pypeclub/OpenPype/pull/3630) - -**🐛 Bug fixes** - -- Maya: fix aov separator in Redshift [\#3625](https://github.com/pypeclub/OpenPype/pull/3625) -- Fix for multi-version build on Mac [\#3622](https://github.com/pypeclub/OpenPype/pull/3622) -- Ftrack: Sync hierarchical attributes can handle new created entities [\#3621](https://github.com/pypeclub/OpenPype/pull/3621) - -**🔀 Refactored code** - -- General: Plugin settings handled by plugins [\#3623](https://github.com/pypeclub/OpenPype/pull/3623) - ## [3.12.2](https://github.com/pypeclub/OpenPype/tree/3.12.2) (2022-07-27) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.12.2-nightly.4...3.12.2) diff --git a/openpype/version.py b/openpype/version.py index 0c114b6060..26b03c37e5 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2-nightly.1" +__version__ = "3.14.2-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 0deb4f465b..f74f40c561 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.2+nightly.1" # OpenPype +version = "3.14.2-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 5645bcb353b13b1711ba67e0a3b394b273e7cef3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 3 Sep 2022 13:17:10 +0200 Subject: [PATCH 201/513] Use custom plugin info per type of plugin submission --- .../plugins/publish/submit_maya_deadline.py | 127 +++++++++--------- 1 file changed, 66 insertions(+), 61 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 68d55fef5d..2a41d92efd 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -49,6 +49,30 @@ class MayaPluginInfo: RenderSetupIncludeLights = attr.ib(default=None) # Include all lights flag +@attr.s +class PythonPluginInfo: + ScriptFile = attr.ib() + Version = attr.ib(default="3.6") + Arguments = attr.ib(default=None) + SingleFrameOnly = attr.ib(default=None) + + +@attr.s +class VRayPluginInfo: + InputFilename = attr.ib(default=None) # Input + SeparateFilesPerFrame = attr.ib(default=None) + VRayEngine = attr.ib(default="V-Ray") + Width = attr.ib(default=None) + Height = attr.ib(default=None) # Mandatory for Deadline + OutputFilePath = attr.ib(default=True) + OutputFileName = attr.ib(default=None) # Render only this layer + + +@attr.s +class ArnoldPluginInfo: + ArnoldFile = attr.ib(default=None) + + class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): label = "Submit Render to Deadline" @@ -479,26 +503,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): def _get_vray_export_payload(self, data): job_info = copy.deepcopy(self.job_info) - job_info.Name = self._job_info_label("Export") # Get V-Ray settings info to compute output path - vray_settings = cmds.ls(type="VRaySettingsNode") - node = vray_settings[0] - template = cmds.getAttr("{}.vrscene_filename".format(node)) - scene, _ = os.path.splitext(data["filename"]) - first_file = self.format_vray_output_filename(scene, template) - first_file = "{}/{}".format(data["workspace"], first_file) - output = os.path.dirname(first_file) + vray_scene = self.format_vray_output_filename() plugin_info = { "Renderer": "vray", "SkipExistingFrames": True, "UseLegacyRenderLayers": True, - "OutputFilePath": output + "OutputFilePath": os.path.dirname(vray_scene) } - return job_info, plugin_info + return job_info, attr.asdict(plugin_info) def _get_arnold_export_payload(self, data): @@ -515,8 +532,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): script = os.path.normpath(module_path) job_info = copy.deepcopy(self.job_info) - plugin_info = copy.deepcopy(self.plugin_info) - job_info.Name = self._job_info_label("Export") # Force a single frame Python job @@ -540,14 +555,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): continue job_info.EnvironmentKeyValue[key] = value - plugin_info.update({ - "Version": "3.6", - "ScriptFile": script, - "Arguments": "", - "SingleFrameOnly": "True", - }) + plugin_info = PythonPluginInfo( + ScriptFile=script, + Version="3.6", + Arguments="", + SingleFrameOnly="True" + ) - return job_info, plugin_info + return job_info, attr.asdict(plugin_info) def _get_vray_render_payload(self, data): @@ -558,27 +573,17 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.OverrideTaskExtraInfoNames = False # Plugin Info - vray_settings = cmds.ls(type="VRaySettingsNode") - node = vray_settings[0] - template = cmds.getAttr("{}.vrscene_filename".format(node)) - # "vrayscene//_/" + plugin_info = VRayPluginInfo( + InputFilename=self.format_vray_output_filename(), + SeparateFilesPerFrame=False, + VRayEngine="V-Ray", + Width=self._instance.data["resolutionWidth"], + Height=self._instance.data["resolutionHeight"], + OutputFilePath=job_info.OutputDirectory[0], + OutputFileName=job_info.OutputFilename[0] + ) - scene, _ = os.path.splitext(self.scene_path) - first_file = self.format_vray_output_filename(scene, template) - first_file = "{}/{}".format(data["workspace"], first_file) - - plugin_info = { - "InputFilename": first_file, - "SeparateFilesPerFrame": True, - "VRayEngine": "V-Ray", - - "Width": self._instance.data["resolutionWidth"], - "Height": self._instance.data["resolutionHeight"], - "OutputFilePath": job_info.OutputDirectory[0], - "OutputFileName": job_info.OutputFilename[0] - } - - return job_info, plugin_info + return job_info, attr.asdict(plugin_info) def _get_arnold_render_payload(self, data): @@ -590,55 +595,55 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): # Plugin Info ass_file, _ = os.path.splitext(data["output_filename_0"]) - first_file = ass_file + ".ass" - plugin_info = { - "ArnoldFile": first_file, - } + ass_filepath = ass_file + ".ass" - return job_info, plugin_info + plugin_info = ArnoldPluginInfo( + ArnoldFile=ass_filepath + ) - def format_vray_output_filename(self, filename, template, dir=False): + return job_info, attr.asdict(plugin_info) + + def format_vray_output_filename(self): """Format the expected output file of the Export job. Example: /_/ - "shot010_v006/shot010_v006_CHARS/CHARS" - - Args: - instance: - filename(str): - dir(bool): - + "shot010_v006/shot010_v006_CHARS/CHARS_0001.vrscene" Returns: str """ + + # "vrayscene//_/" + vray_settings = cmds.ls(type="VRaySettingsNode") + node = vray_settings[0] + template = cmds.getAttr("{}.vrscene_filename".format(node)) + scene, _ = os.path.splitext(self.scene_path) + def smart_replace(string, key_values): new_string = string for key, value in key_values.items(): new_string = new_string.replace(key, value) return new_string - # Ensure filename has no extension - file_name, _ = os.path.splitext(filename) + # Get workfile scene path without extension to format vrscene_filename + scene_filename = os.path.basename(self.scene_path) + scene_filename_no_ext, _ = os.path.splitext(scene_filename) layer = self._instance.data['setMembers'] # Reformat without tokens output_path = smart_replace( template, - {"": file_name, + {"": scene_filename_no_ext, "": layer}) - if dir: - return output_path.replace("\\", "/") - start_frame = int(self._instance.data["frameStartHandle"]) + workspace = self._instance.context.data["workspace"] filename_zero = "{}_{:04d}.vrscene".format(output_path, start_frame) + filepath_zero = os.path.join(workspace, filename_zero) - result = filename_zero.replace("\\", "/") - - return result + return filepath_zero.replace("\\", "/") def _patch_workfile(self): """Patch Maya scene. From 507dac4aa9f50e8978a841067262ce33e77cf5e0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 3 Sep 2022 15:06:24 +0200 Subject: [PATCH 202/513] Ensure integer math for _format_tiles See #3758 --- .../plugins/publish/submit_maya_deadline.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 2a41d92efd..7c486b7c34 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -754,14 +754,21 @@ def _format_tiles( used for assembler configuration. """ - tile = 0 + # Math used requires integers for correct output - as such + # we ensure our inputs are correct. + assert type(tiles_x) is int, "tiles_x must be an integer" + assert type(tiles_y) is int, "tiles_y must be an integer" + assert type(width) is int, "width must be an integer" + assert type(height) is int, "height must be an integer" + out = {"JobInfo": {}, "PluginInfo": {}} cfg = OrderedDict() - w_space = width / tiles_x - h_space = height / tiles_y + w_space = width // tiles_x + h_space = height // tiles_y cfg["TilesCropped"] = "False" + tile = 0 for tile_x in range(1, tiles_x + 1): for tile_y in reversed(range(1, tiles_y + 1)): tile_prefix = "_tile_{}x{}_{}x{}_".format( @@ -769,10 +776,10 @@ def _format_tiles( tiles_x, tiles_y ) - top = int(height - (tile_y * h_space)) - bottom = int(height - ((tile_y - 1) * h_space) - 1) - left = int((tile_x - 1) * w_space) - right = int((tile_x * w_space) - 1) + top = height - (tile_y * h_space) + bottom = height - ((tile_y - 1) * h_space) - 1 + left = (tile_x - 1) * w_space + right = (tile_x * w_space) - 1 # Job Info new_filename = "{}/{}{}".format( From f54f4cf99c824e927e5cbe24c7a02ec0e4a4fc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Sun, 4 Sep 2022 22:43:25 +0200 Subject: [PATCH 203/513] Kitsu : Modification default value for Ignore_projects --- openpype/modules/kitsu/utils/update_op_with_zou.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 199c59053b..26cd125e15 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -318,7 +318,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects(login: str, password: str, ignore_projects=[]): +def sync_all_projects(login: str, password: str, ignore_projects: list = None): """Update all OP projects in DB with Zou data. Args: @@ -340,8 +340,9 @@ def sync_all_projects(login: str, password: str, ignore_projects=[]): dbcon.install() all_projects = gazu.project.all_open_projects() for project in all_projects: - if project["name"] not in ignore_projects: - sync_project_from_kitsu(dbcon, project) + if ignore_projects and project["name"] in ignore_projects: + continue + sync_project_from_kitsu(dbcon, project) def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): From fb48faf386c8d53c737ac9a6a00287730c934217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Albert?= Date: Sun, 4 Sep 2022 22:49:07 +0200 Subject: [PATCH 204/513] Kitsu : Ignore_projects - minor fix - indent --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 26cd125e15..55a7bdc51d 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -341,7 +341,7 @@ def sync_all_projects(login: str, password: str, ignore_projects: list = None): all_projects = gazu.project.all_open_projects() for project in all_projects: if ignore_projects and project["name"] in ignore_projects: - continue + continue sync_project_from_kitsu(dbcon, project) From 81d8a53dbad5313fc435ca5669500e868f4c1134 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Sep 2022 12:20:16 +0200 Subject: [PATCH 205/513] fix function name --- openpype/client/__init__.py | 4 ++-- openpype/lib/avalon_context.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index cf3ce1ae46..7831afd8ad 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -48,7 +48,7 @@ from .entities import ( from .entity_links import ( get_linked_asset_ids, get_linked_assets, - get_linked_representation_ids, + get_linked_representation_id, ) from .operations import ( @@ -102,7 +102,7 @@ __all__ = ( "get_linked_asset_ids", "get_linked_assets", - "get_linked_representation_ids", + "get_linked_representation_id", "create_project", ) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 470c40d0d7..c890e08d3e 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1061,14 +1061,14 @@ def get_linked_ids_for_representations( Function will be removed after release version 3.16.* """ - from openpype.client import get_linked_representation_ids + from openpype.client import get_linked_representation_id if not isinstance(repre_ids, list): repre_ids = [repre_ids] output = [] for repre_id in repre_ids: - output.extend(get_linked_representation_ids( + output.extend(get_linked_representation_id( project_name, repre_id=repre_id, link_type=link_type, From 88ab0462b5b8861f99ef0eb069d8f1e2ff4ccfd3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Sep 2022 12:21:19 +0200 Subject: [PATCH 206/513] fix new import path --- openpype/lib/avalon_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index c890e08d3e..12f4a5198b 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1039,7 +1039,7 @@ def get_last_workfile( ) -@deprecated("openpype.client.get_linked_ids_for_representations") +@deprecated("openpype.client.get_linked_representation_id") def get_linked_ids_for_representations( project_name, repre_ids, dbcon=None, link_type=None, max_depth=0 ): From 469ba24879956e5c1a83d6d042f6503d3e254593 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 Sep 2022 12:27:23 +0200 Subject: [PATCH 207/513] Fix - updated to not use deprecated function --- openpype/plugins/load/add_site.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 388a871e9d..ac931e41db 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -1,4 +1,4 @@ -from openpype.client import get_linked_ids_for_representations +from openpype.client import get_linked_representation_id from openpype.modules import ModulesManager from openpype.pipeline import load from openpype.modules.sync_server.utils import SiteAlreadyPresentError @@ -45,7 +45,7 @@ class AddSyncSite(load.LoaderPlugin): force=True) if family == "workfile": - links = get_linked_ids_for_representations( + links = get_linked_representation_id( project_name, repre_id=repre_id, link_type="reference" From db1fa6d40ef59f9e3061a637a04874d4857a6585 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 5 Sep 2022 12:49:43 +0200 Subject: [PATCH 208/513] add a python2 compatibility for the FileNotFoundError --- openpype/hosts/houdini/api/shelves.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 805ce4c397..248d99105c 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,6 +1,7 @@ import os import logging import platform +import six from openpype.settings import get_project_settings @@ -8,6 +9,9 @@ import hou log = logging.getLogger("openpype.hosts.houdini.shelves") +if six.PY2: + FileNotFoundError = IOError + def generate_shelves(): """This function generates complete shelves from shelf set to tools From d9a150022e1659aec584fe962f9c47e66bfb178d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Sep 2022 19:28:48 +0800 Subject: [PATCH 209/513] adding and loading maya mel workspace through openpype project setting --- openpype/hosts/maya/api/pipeline.py | 4 ++-- openpype/hosts/maya/lib.py | 18 ++++++++++++++++++ .../defaults/project_settings/maya.json | 14 ++++++++++++++ .../projects_schema/schema_project_maya.json | 15 +++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index f565f6a308..5bf8b67fc2 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -28,7 +28,7 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.load import any_outdated_containers -from openpype.hosts.maya.lib import copy_workspace_mel +from openpype.hosts.maya.lib import copy_workspace_mel,load_workspace_mel from . import menu, lib from .workio import ( open_file, @@ -550,7 +550,7 @@ def on_task_changed(): def before_workfile_save(event): workdir_path = event["workdir_path"] if workdir_path: - copy_workspace_mel(workdir_path) + load_workspace_mel(workdir_path) class MayaDirmap(HostDirmap): diff --git a/openpype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py index 6c142053e6..d24f267bbd 100644 --- a/openpype/hosts/maya/lib.py +++ b/openpype/hosts/maya/lib.py @@ -1,6 +1,8 @@ import os import shutil +import json +from openpype.settings import get_current_project_settings def copy_workspace_mel(workdir): # Check that source mel exists @@ -24,3 +26,19 @@ def copy_workspace_mel(workdir): src_filepath, dst_filepath )) shutil.copy(src_filepath, dst_filepath) + + +def load_workspace_mel(workdir): + dst_filepath = os.path.join(workdir, "workspace.mel") + if os.path.exists(dst_filepath): + return + + if not os.path.exists(workdir): + os.makedirs(workdir) + + with open(dst_filepath, "w") as mel_file: + setting = get_current_project_settings() + mel_script = setting["maya"]["mel-workspace"]["scripts"] + for mel in mel_script: + mel_file.write(mel) + mel_file.write("\n") diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index ac0f161cf2..0a46632042 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -15,6 +15,20 @@ "destination-path": [] } }, + "mel-workspace":{ + "scripts":[ + "workspace -fr \"shaders\" \"renderData/shaders\";", + "workspace -fr \"images\" \"renders\";", + "workspace -fr \"particles\" \"particles\";", + "workspace -fr \"mayaAscii\" \"\";", + "workspace -fr \"mayaBinary\" \"\";", + "workspace -fr \"scene\" \"\";", + "workspace -fr \"alembicCache\" \"cache/alembic\";", + "workspace -fr \"renderData\" \"renderData\";", + "workspace -fr \"sourceImages\" \"sourceimages\";", + "workspace -fr \"fileCache\" \"cache/nCache\";" + ] + }, "scriptsmenu": { "name": "OpenPype Tools", "definition": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index cb380194a7..a774d604ca 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -53,6 +53,21 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "mel-workspace", + "label": "Maya MEL Workspace", + "is_group": true, + "children": [ + { + "type": "list", + "object_type": "text", + "key": "scripts", + "label": "scripts" + } + ] + }, { "type": "schema", "name": "schema_scriptsmenu" From b88def9aea1fc1a682209ea78edcf5ae87a652e3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Sep 2022 19:40:30 +0800 Subject: [PATCH 210/513] adding and loading maya mel workspace through openpype project setting --- openpype/hosts/maya/lib.py | 27 +------------------ .../defaults/project_settings/maya.json | 2 +- .../projects_schema/schema_project_maya.json | 4 +-- 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/openpype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py index d24f267bbd..bf06c9ad7d 100644 --- a/openpype/hosts/maya/lib.py +++ b/openpype/hosts/maya/lib.py @@ -1,33 +1,8 @@ import os import shutil -import json from openpype.settings import get_current_project_settings -def copy_workspace_mel(workdir): - # Check that source mel exists - current_dir = os.path.dirname(os.path.abspath(__file__)) - src_filepath = os.path.join(current_dir, "resources", "workspace.mel") - if not os.path.exists(src_filepath): - print("Source mel file does not exist. {}".format(src_filepath)) - return - - # Skip if workspace.mel already exists - dst_filepath = os.path.join(workdir, "workspace.mel") - if os.path.exists(dst_filepath): - return - - # Create workdir if does not exists yet - if not os.path.exists(workdir): - os.makedirs(workdir) - - # Copy file - print("Copying workspace mel \"{}\" -> \"{}\"".format( - src_filepath, dst_filepath - )) - shutil.copy(src_filepath, dst_filepath) - - def load_workspace_mel(workdir): dst_filepath = os.path.join(workdir, "workspace.mel") if os.path.exists(dst_filepath): @@ -38,7 +13,7 @@ def load_workspace_mel(workdir): with open(dst_filepath, "w") as mel_file: setting = get_current_project_settings() - mel_script = setting["maya"]["mel-workspace"]["scripts"] + mel_script = setting["maya"]["mel-workspace"]["definition"] for mel in mel_script: mel_file.write(mel) mel_file.write("\n") diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 0a46632042..162732280f 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -16,7 +16,7 @@ } }, "mel-workspace":{ - "scripts":[ + "definition":[ "workspace -fr \"shaders\" \"renderData/shaders\";", "workspace -fr \"images\" \"renders\";", "workspace -fr \"particles\" \"particles\";", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index a774d604ca..7204ec586a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -63,8 +63,8 @@ { "type": "list", "object_type": "text", - "key": "scripts", - "label": "scripts" + "key": "definition", + "label": "definition" } ] }, From 109abb58987b22f6d390d424a27e209eff6b5638 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Sep 2022 19:41:09 +0800 Subject: [PATCH 211/513] adding and loading maya mel workspace through openpype project setting --- openpype/hosts/maya/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 5bf8b67fc2..4768a9ee4f 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -28,7 +28,7 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.load import any_outdated_containers -from openpype.hosts.maya.lib import copy_workspace_mel,load_workspace_mel +from openpype.hosts.maya.lib import load_workspace_mel from . import menu, lib from .workio import ( open_file, From 69d2cf20f5b4889ce674487d2da8fd2a230a093a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Sep 2022 19:42:10 +0800 Subject: [PATCH 212/513] adding and loading maya mel workspace through openpype project setting --- openpype/hosts/maya/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py index bf06c9ad7d..2853789656 100644 --- a/openpype/hosts/maya/lib.py +++ b/openpype/hosts/maya/lib.py @@ -3,6 +3,7 @@ import shutil from openpype.settings import get_current_project_settings + def load_workspace_mel(workdir): dst_filepath = os.path.join(workdir, "workspace.mel") if os.path.exists(dst_filepath): From 5908f49b685a981d08d7ea1ff841d567018a7e76 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Sep 2022 10:56:01 +0200 Subject: [PATCH 213/513] updating README file --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b8c04f8b49..a2f442b640 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ It can be built and ran on all common platforms. We develop and test on the foll - **Linux** - **Ubuntu** 20.04 LTS - **Centos** 7 -- **Mac OSX** +- **Mac OSX** - **10.15** Catalina - **11.1** Big Sur (using Rosetta2) @@ -287,6 +287,14 @@ To run tests, execute `.\tools\run_tests(.ps1|.sh)`. **Note that it needs existing virtual environment.** + +Developer tools +------------- + +In case you wish to add your own tools to `.\tools` folder without git tracking, it is possible by adding it with `dev_*` suffix (`dev_clear_pyc(.ps1|.sh)`). + + + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): From a0e241b02fbfdee18b8ded65af89eedb343793d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Sep 2022 10:58:22 +0200 Subject: [PATCH 214/513] README fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2f442b640..a3d3cf1dbb 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,7 @@ To run tests, execute `.\tools\run_tests(.ps1|.sh)`. Developer tools ------------- -In case you wish to add your own tools to `.\tools` folder without git tracking, it is possible by adding it with `dev_*` suffix (`dev_clear_pyc(.ps1|.sh)`). +In case you wish to add your own tools to `.\tools` folder without git tracking, it is possible by adding it with `dev_*` suffix (example: `dev_clear_pyc(.ps1|.sh)`). From 70a17a8876541de84d98bd8f502421827fd78751 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Sep 2022 11:39:58 +0200 Subject: [PATCH 215/513] hiero: instances detection - timeline no need to retime --- openpype/hosts/hiero/plugins/publish/precollect_instances.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 0c7dbc1f22..84f2927fc7 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -318,10 +318,9 @@ class PrecollectInstances(pyblish.api.ContextPlugin): @staticmethod def create_otio_time_range_from_timeline_item_data(track_item): - speed = track_item.playbackSpeed() timeline = phiero.get_current_sequence() frame_start = int(track_item.timelineIn()) - frame_duration = int((track_item.duration() - 1) / speed) + frame_duration = int(track_item.duration()) fps = timeline.framerate().toFloat() return hiero_export.create_otio_time_range( From 1ed5ef7dcdf9cd2183f009c541e4c52f5374075d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 6 Sep 2022 13:16:01 +0200 Subject: [PATCH 216/513] OP-3863 - attempt to speed up ExtractImage If workfile has a large number of layers (hundreds or thousands), ExtractImage wasn't too efficient. It was hiding/showing layers too many times. Current logic is to hide all, show only publishable layers of instance, save to image, hide them again. get_layers replaced by argument if possible. --- openpype/hosts/photoshop/api/lib.py | 11 +- openpype/hosts/photoshop/api/ws_stub.py | 19 ++- .../plugins/publish/extract_image.py | 120 +++++++++++------- 3 files changed, 97 insertions(+), 53 deletions(-) diff --git a/openpype/hosts/photoshop/api/lib.py b/openpype/hosts/photoshop/api/lib.py index 73a546604f..221b4314e6 100644 --- a/openpype/hosts/photoshop/api/lib.py +++ b/openpype/hosts/photoshop/api/lib.py @@ -64,10 +64,15 @@ def maintained_selection(): @contextlib.contextmanager -def maintained_visibility(): - """Maintain visibility during context.""" +def maintained_visibility(layers=None): + """Maintain visibility during context. + + Args: + layers (list) of PSItem (used for caching) + """ visibility = {} - layers = stub().get_layers() + if not layers: + layers = stub().get_layers() for layer in layers: visibility[layer.id] = layer.visible try: diff --git a/openpype/hosts/photoshop/api/ws_stub.py b/openpype/hosts/photoshop/api/ws_stub.py index b49bf1c73f..2c4d0ad5fc 100644 --- a/openpype/hosts/photoshop/api/ws_stub.py +++ b/openpype/hosts/photoshop/api/ws_stub.py @@ -229,10 +229,11 @@ class PhotoshopServerStub: return self._get_layers_in_layers(parent_ids) - def get_layers_in_layers_ids(self, layers_ids): + def get_layers_in_layers_ids(self, layers_ids, layers=None): """Return all layers that belong to layers (might be groups). Args: + layers_ids layers : Returns: @@ -240,10 +241,13 @@ class PhotoshopServerStub: """ parent_ids = set(layers_ids) - return self._get_layers_in_layers(parent_ids) + return self._get_layers_in_layers(parent_ids, layers) - def _get_layers_in_layers(self, parent_ids): - all_layers = self.get_layers() + def _get_layers_in_layers(self, parent_ids, layers=None): + if not layers: + layers = self.get_layers() + + all_layers = layers ret = [] for layer in all_layers: @@ -394,14 +398,17 @@ class PhotoshopServerStub: self.hide_all_others_layers_ids(extract_ids) - def hide_all_others_layers_ids(self, extract_ids): + def hide_all_others_layers_ids(self, extract_ids, layers=None): """hides all layers that are not part of the list or that are not children of this list Args: extract_ids (list): list of integer that should be visible + layers (list) of PSItem (used for caching) """ - for layer in self.get_layers(): + if not layers: + layers = self.get_layers() + for layer in layers: if layer.visible and layer.id not in extract_ids: self.set_visible(layer.id, False) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py index a133e33409..7543af95bd 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -1,61 +1,93 @@ import os -import openpype.api +import pyblish.api + +from openpype.pipeline import publish from openpype.hosts.photoshop import api as photoshop -class ExtractImage(openpype.api.Extractor): - """Produce a flattened image file from instance - - This plug-in takes into account only the layers in the group. - """ +class ExtractImage(pyblish.api.ContextPlugin): + """Save scene before extraction.""" + order = publish.Extractor.order - 0.48 label = "Extract Image" hosts = ["photoshop"] + families = ["image", "background"] formats = ["png", "jpg"] - def process(self, instance): - staging_dir = self.staging_dir(instance) - self.log.info("Outputting image to {}".format(staging_dir)) - - # Perform extraction + def process(self, context): stub = photoshop.stub() - files = {} + hidden_layer_ids = set() + + all_layers = stub.get_layers() + for layer in all_layers: + if not layer.visible: + hidden_layer_ids.add(layer.id) + stub.hide_all_others_layers_ids([], layers=all_layers) + with photoshop.maintained_selection(): - self.log.info("Extracting %s" % str(list(instance))) - with photoshop.maintained_visibility(): - ids = set() - layer = instance.data.get("layer") - if layer: - ids.add(layer.id) - add_ids = instance.data.pop("ids", None) - if add_ids: - ids.update(set(add_ids)) - extract_ids = set([ll.id for ll in stub. - get_layers_in_layers_ids(ids)]) - stub.hide_all_others_layers_ids(extract_ids) + # self.log.info("Extracting %s" % str(list(instance))) + with photoshop.maintained_visibility(layers=all_layers): + for instance in context: + if instance.data["family"] not in self.families: + continue - file_basename = os.path.splitext( - stub.get_active_document_name() - )[0] - for extension in self.formats: - _filename = "{}.{}".format(file_basename, extension) - files[extension] = _filename + staging_dir = self.staging_dir(instance) + self.log.info("Outputting image to {}".format(staging_dir)) - full_filename = os.path.join(staging_dir, _filename) - stub.saveAs(full_filename, extension, True) - self.log.info(f"Extracted: {extension}") + # Perform extraction + files = {} + ids = set() + layer = instance.data.get("layer") + if layer: + ids.add(layer.id) + add_ids = instance.data.pop("ids", None) + if add_ids: + ids.update(set(add_ids)) + extract_ids = set([ll.id for ll in stub. + get_layers_in_layers_ids(ids, all_layers) + if ll.id not in hidden_layer_ids]) - representations = [] - for extension, filename in files.items(): - representations.append({ - "name": extension, - "ext": extension, - "files": filename, - "stagingDir": staging_dir - }) - instance.data["representations"] = representations - instance.data["stagingDir"] = staging_dir + for extracted_id in extract_ids: + stub.set_visible(extracted_id, True) - self.log.info(f"Extracted {instance} to {staging_dir}") + file_basename = os.path.splitext( + stub.get_active_document_name() + )[0] + for extension in self.formats: + _filename = "{}.{}".format(file_basename, + extension) + files[extension] = _filename + + full_filename = os.path.join(staging_dir, + _filename) + stub.saveAs(full_filename, extension, True) + self.log.info(f"Extracted: {extension}") + + representations = [] + for extension, filename in files.items(): + representations.append({ + "name": extension, + "ext": extension, + "files": filename, + "stagingDir": staging_dir + }) + instance.data["representations"] = representations + instance.data["stagingDir"] = staging_dir + + self.log.info(f"Extracted {instance} to {staging_dir}") + + for extracted_id in extract_ids: + stub.set_visible(extracted_id, False) + + def staging_dir(self, instance): + """Provide a temporary directory in which to store extracted files + + Upon calling this method the staging directory is stored inside + the instance.data['stagingDir'] + """ + + from openpype.pipeline.publish import get_instance_staging_dir + + return get_instance_staging_dir(instance) From d00eb29ca1bd8fcda97a40a14f8ae03d171c4853 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Sep 2022 13:20:48 +0200 Subject: [PATCH 217/513] Remove instance._log legacy code that has no use anymore --- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 2647dcf0cb..c9d1daffd1 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -700,9 +700,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.context = context self.anatomy = instance.context.data["anatomy"] - if hasattr(instance, "_log"): - data['_log'] = instance._log - asset = data.get("asset") or legacy_io.Session["AVALON_ASSET"] subset = data.get("subset") From 96f8c3b7b3a1cf524c2ed1a815092ef2f55a2e9b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 6 Sep 2022 13:23:41 +0200 Subject: [PATCH 218/513] OP-3863 - fixed doc string --- .../hosts/photoshop/plugins/publish/extract_image.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py index 7543af95bd..5bdb3ef681 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -7,7 +7,15 @@ from openpype.hosts.photoshop import api as photoshop class ExtractImage(pyblish.api.ContextPlugin): - """Save scene before extraction.""" + """Extract all layers (groups) marked for publish. + + Usually publishable instance is created as a wrapper of layer(s). For each + publishable instance so many images as there is 'formats' is created. + + Logic tries to hide/unhide layers minimum times. + + Called once for all publishable instances. + """ order = publish.Extractor.order - 0.48 label = "Extract Image" @@ -27,7 +35,6 @@ class ExtractImage(pyblish.api.ContextPlugin): stub.hide_all_others_layers_ids([], layers=all_layers) with photoshop.maintained_selection(): - # self.log.info("Extracting %s" % str(list(instance))) with photoshop.maintained_visibility(layers=all_layers): for instance in context: if instance.data["family"] not in self.families: From c8efd0d67e5600a093d024786dd16717c9f3d16c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 6 Sep 2022 17:07:46 +0200 Subject: [PATCH 219/513] removed f-string formatting for py2 compatibility --- openpype/hosts/resolve/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index 382a7cf344..aa8ad3008b 100644 --- a/openpype/hosts/resolve/utils.py +++ b/openpype/hosts/resolve/utils.py @@ -17,7 +17,7 @@ def setup(env): # collect script dirs if us_env: - log.info(f"Utility Scripts Env: `{us_env}`") + log.info("Utility Scripts Env: `{}`".format(us_env)) us_paths = us_env.split( os.pathsep) + us_paths @@ -25,8 +25,8 @@ def setup(env): for path in us_paths: scripts.update({path: os.listdir(path)}) - log.info(f"Utility Scripts Dir: `{us_paths}`") - log.info(f"Utility Scripts: `{scripts}`") + log.info("Utility Scripts Dir: `{}`".format(us_paths)) + log.info("Utility Scripts: `{}`".format(scripts)) # make sure no script file is in folder for s in os.listdir(us_dir): @@ -44,7 +44,7 @@ def setup(env): # script in script list src = os.path.join(d, s) dst = os.path.join(us_dir, s) - log.info(f"Copying `{src}` to `{dst}`...") + log.info("Copying `{}` to `{}`...".format(src, dst)) if os.path.isdir(src): shutil.copytree( src, dst, symlinks=False, From de52a7d61b970b6df353130295568a2242b6b902 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 6 Sep 2022 17:21:32 +0200 Subject: [PATCH 220/513] last f-string removement --- openpype/hosts/resolve/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index aa8ad3008b..d5c133bbf5 100644 --- a/openpype/hosts/resolve/utils.py +++ b/openpype/hosts/resolve/utils.py @@ -31,7 +31,7 @@ def setup(env): # make sure no script file is in folder for s in os.listdir(us_dir): path = os.path.join(us_dir, s) - log.info(f"Removing `{path}`...") + log.info("Removing `{}`...".format(path)) if os.path.isdir(path): shutil.rmtree(path, onerror=None) else: From 915cba631c42a822be8f71517d081a43a09cc177 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 7 Sep 2022 04:24:55 +0000 Subject: [PATCH 221/513] [Automated] Bump version --- CHANGELOG.md | 18 +++++++++--------- openpype/version.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b35e89e96e..6754f1e2e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,25 @@ # Changelog -## [3.14.2-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.2-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) **🆕 New features** - Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) -- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) **🚀 Enhancements** +- Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) - SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) +- Blender: Publisher collect workfile representation [\#3670](https://github.com/pypeclub/OpenPype/pull/3670) - Maya: move set render settings menu entry [\#3669](https://github.com/pypeclub/OpenPype/pull/3669) - Scene Inventory: Maya add actions to select from or to scene [\#3659](https://github.com/pypeclub/OpenPype/pull/3659) **🐛 Bug fixes** +- Resolve: Addon import is Python 2 compatible [\#3798](https://github.com/pypeclub/OpenPype/pull/3798) +- nuke: validate write node is not failing due wrong type [\#3780](https://github.com/pypeclub/OpenPype/pull/3780) - Fix - changed format of version string in pyproject.toml [\#3777](https://github.com/pypeclub/OpenPype/pull/3777) - Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) - Fix version resolution [\#3757](https://github.com/pypeclub/OpenPype/pull/3757) @@ -24,10 +27,14 @@ **🔀 Refactored code** +- Photoshop: Use new Extractor location [\#3789](https://github.com/pypeclub/OpenPype/pull/3789) +- Blender: Use new Extractor location [\#3787](https://github.com/pypeclub/OpenPype/pull/3787) - AfterEffects: Use new Extractor location [\#3784](https://github.com/pypeclub/OpenPype/pull/3784) - General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) - General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) +- General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) - General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) +- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) - General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) @@ -80,7 +87,6 @@ **🔀 Refactored code** -- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) @@ -116,25 +122,19 @@ - Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) - Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) - Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) -- General: Optimized OCIO configs [\#3650](https://github.com/pypeclub/OpenPype/pull/3650) **🐛 Bug fixes** - General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) - General: Fix finding of last version [\#3656](https://github.com/pypeclub/OpenPype/pull/3656) -- General: Extract Review can scale with pixel aspect ratio [\#3644](https://github.com/pypeclub/OpenPype/pull/3644) -- Maya: Refactor moved usage of CreateRender settings [\#3643](https://github.com/pypeclub/OpenPype/pull/3643) **🔀 Refactored code** - General: Use client projects getter [\#3673](https://github.com/pypeclub/OpenPype/pull/3673) -- Resolve: Match folder structure to other hosts [\#3653](https://github.com/pypeclub/OpenPype/pull/3653) -- Maya: Hosts as modules [\#3647](https://github.com/pypeclub/OpenPype/pull/3647) **Merged pull requests:** - Deadline: Global job pre load is not Pype 2 compatible [\#3666](https://github.com/pypeclub/OpenPype/pull/3666) -- Maya: Remove unused get current renderer logic [\#3645](https://github.com/pypeclub/OpenPype/pull/3645) ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) diff --git a/openpype/version.py b/openpype/version.py index 26b03c37e5..c042ca2625 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2-nightly.2" +__version__ = "3.14.2-nightly.3" From 26fbdac8da117c83a71b75ce6315be4044d23942 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Sep 2022 16:14:30 +0800 Subject: [PATCH 222/513] adding and loading maya mel workspace through openpype project setting --- openpype/hosts/maya/api/pipeline.py | 5 +++-- openpype/hosts/maya/hooks/pre_copy_mel.py | 5 +++-- openpype/hosts/maya/lib.py | 19 ++++++++++--------- .../defaults/project_anatomy/attributes.json | 3 +-- .../defaults/project_settings/maya.json | 15 +-------------- .../projects_schema/schema_project_maya.json | 17 ++++------------- 6 files changed, 22 insertions(+), 42 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 4768a9ee4f..4578d6fb39 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -28,7 +28,7 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.load import any_outdated_containers -from openpype.hosts.maya.lib import load_workspace_mel +from openpype.hosts.maya.lib import create_workspace_mel from . import menu, lib from .workio import ( open_file, @@ -548,9 +548,10 @@ def on_task_changed(): def before_workfile_save(event): + project_name = os.getenv("AVALON_PROJECT") workdir_path = event["workdir_path"] if workdir_path: - load_workspace_mel(workdir_path) + create_workspace_mel(workdir_path, project_name) class MayaDirmap(HostDirmap): diff --git a/openpype/hosts/maya/hooks/pre_copy_mel.py b/openpype/hosts/maya/hooks/pre_copy_mel.py index b11e18241e..6f90af4b7c 100644 --- a/openpype/hosts/maya/hooks/pre_copy_mel.py +++ b/openpype/hosts/maya/hooks/pre_copy_mel.py @@ -1,5 +1,5 @@ from openpype.lib import PreLaunchHook -from openpype.hosts.maya.lib import copy_workspace_mel +from openpype.hosts.maya.lib import create_workspace_mel class PreCopyMel(PreLaunchHook): @@ -10,9 +10,10 @@ class PreCopyMel(PreLaunchHook): app_groups = ["maya"] def execute(self): + project_name = self.launch_context.env.get("AVALON_PROJECT") workdir = self.launch_context.env.get("AVALON_WORKDIR") if not workdir: self.log.warning("BUG: Workdir is not filled.") return - copy_workspace_mel(workdir) + create_workspace_mel(workdir, project_name) diff --git a/openpype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py index 2853789656..443bf7d10e 100644 --- a/openpype/hosts/maya/lib.py +++ b/openpype/hosts/maya/lib.py @@ -1,10 +1,8 @@ import os -import shutil - -from openpype.settings import get_current_project_settings +from openpype.settings import get_project_settings -def load_workspace_mel(workdir): +def create_workspace_mel(workdir, project_name): dst_filepath = os.path.join(workdir, "workspace.mel") if os.path.exists(dst_filepath): return @@ -12,9 +10,12 @@ def load_workspace_mel(workdir): if not os.path.exists(workdir): os.makedirs(workdir) + project_setting = get_project_settings(project_name) + mel_script = project_setting["maya"].get("mel_workspace") + + # Skip if mel script in settings is empty + if not mel_script: + return + with open(dst_filepath, "w") as mel_file: - setting = get_current_project_settings() - mel_script = setting["maya"]["mel-workspace"]["definition"] - for mel in mel_script: - mel_file.write(mel) - mel_file.write("\n") + mel_file.write(mel_script) diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json index 983ac603f9..bf8bbef8de 100644 --- a/openpype/settings/defaults/project_anatomy/attributes.json +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -19,8 +19,7 @@ "blender/2-91", "harmony/20", "photoshop/2021", - "aftereffects/2021", - "unreal/4-26" + "aftereffects/2021" ], "tools_env": [], "active": true diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 162732280f..ada69c3730 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -15,20 +15,7 @@ "destination-path": [] } }, - "mel-workspace":{ - "definition":[ - "workspace -fr \"shaders\" \"renderData/shaders\";", - "workspace -fr \"images\" \"renders\";", - "workspace -fr \"particles\" \"particles\";", - "workspace -fr \"mayaAscii\" \"\";", - "workspace -fr \"mayaBinary\" \"\";", - "workspace -fr \"scene\" \"\";", - "workspace -fr \"alembicCache\" \"cache/alembic\";", - "workspace -fr \"renderData\" \"renderData\";", - "workspace -fr \"sourceImages\" \"sourceimages\";", - "workspace -fr \"fileCache\" \"cache/nCache\";" - ] - }, + "mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\n", "scriptsmenu": { "name": "OpenPype Tools", "definition": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 7204ec586a..978de56a51 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -54,19 +54,10 @@ ] }, { - "type": "dict", - "collapsible": true, - "key": "mel-workspace", - "label": "Maya MEL Workspace", - "is_group": true, - "children": [ - { - "type": "list", - "object_type": "text", - "key": "definition", - "label": "definition" - } - ] + "type": "text", + "multiline" : true, + "key": "mel_workspace", + "label": "Maya MEL Workspace" }, { "type": "schema", From a9b69536cac401221cacaaa3155c4f9a7be682b8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Sep 2022 16:44:29 +0800 Subject: [PATCH 223/513] adding and loading maya mel workspace through openpype project setting --- openpype/settings/defaults/project_anatomy/attributes.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json index bf8bbef8de..983ac603f9 100644 --- a/openpype/settings/defaults/project_anatomy/attributes.json +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -19,7 +19,8 @@ "blender/2-91", "harmony/20", "photoshop/2021", - "aftereffects/2021" + "aftereffects/2021", + "unreal/4-26" ], "tools_env": [], "active": true From decc11251854f60db02531f93e2b8fbd4d3fa7ec Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Sep 2022 17:06:47 +0800 Subject: [PATCH 224/513] load and edit mel workspace within the Openpype project settings --- .../settings/defaults/project_settings/maya.json | 2 +- .../schemas/projects_schema/schema_project_maya.json | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index ada69c3730..bb96fcf741 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1,4 +1,5 @@ { + "mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\n", "ext_mapping": { "model": "ma", "mayaAscii": "ma", @@ -15,7 +16,6 @@ "destination-path": [] } }, - "mel_workspace": "workspace -fr \"shaders\" \"renderData/shaders\";\nworkspace -fr \"images\" \"renders\";\nworkspace -fr \"particles\" \"particles\";\nworkspace -fr \"mayaAscii\" \"\";\nworkspace -fr \"mayaBinary\" \"\";\nworkspace -fr \"scene\" \"\";\nworkspace -fr \"alembicCache\" \"cache/alembic\";\nworkspace -fr \"renderData\" \"renderData\";\nworkspace -fr \"sourceImages\" \"sourceimages\";\nworkspace -fr \"fileCache\" \"cache/nCache\";\n", "scriptsmenu": { "name": "OpenPype Tools", "definition": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 978de56a51..a54f8e6e4f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -5,6 +5,12 @@ "label": "Maya", "is_file": true, "children": [ + { + "type": "text", + "multiline" : true, + "key": "mel_workspace", + "label": "Maya MEL Workspace" + }, { "type": "dict-modifiable", "key": "ext_mapping", @@ -53,12 +59,6 @@ } ] }, - { - "type": "text", - "multiline" : true, - "key": "mel_workspace", - "label": "Maya MEL Workspace" - }, { "type": "schema", "name": "schema_scriptsmenu" From e1c17c71d30a38bcfda292393e425651c4c5a6d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 7 Sep 2022 12:11:06 +0200 Subject: [PATCH 225/513] fix variable name --- openpype/pipeline/template_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/template_data.py b/openpype/pipeline/template_data.py index bab46a627d..627eba5c3d 100644 --- a/openpype/pipeline/template_data.py +++ b/openpype/pipeline/template_data.py @@ -53,7 +53,7 @@ def get_project_template_data(project_doc=None, project_name=None): project_name = project_doc["name"] if not project_doc: - project_code = get_project(project_name, fields=["data.code"]) + project_doc = get_project(project_name, fields=["data.code"]) project_code = project_doc.get("data", {}).get("code") return { From 509c209093d6fd971ec3631e662a8e34a31c5717 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 7 Sep 2022 12:40:34 +0200 Subject: [PATCH 226/513] fix status handling --- igniter/install_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index b09529f5c5..c7e9ef74c5 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -389,7 +389,7 @@ class InstallDialog(QtWidgets.QDialog): def _installation_finished(self): status = self._install_thread.result() - if status >= 0: + if status is not None and status >= 0: self._update_progress(100) QtWidgets.QApplication.processEvents() self.done(3) From d6ab41887a37e1bfb4e7f9a1f430daef93182faa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 7 Sep 2022 13:41:30 +0200 Subject: [PATCH 227/513] added comment to code --- igniter/install_dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index c7e9ef74c5..65ddd58735 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -388,6 +388,9 @@ class InstallDialog(QtWidgets.QDialog): install_thread.start() def _installation_finished(self): + # TODO we should find out why status can be set to 'None'? + # - 'InstallThread.run' should handle all cases so not sure where + # that come from status = self._install_thread.result() if status is not None and status >= 0: self._update_progress(100) From 170c35c4d11936e40e40b3a36967c558cee3caa9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 7 Sep 2022 14:37:00 +0200 Subject: [PATCH 228/513] initial commit - not changing current implementation yet --- .../pipeline/workfile/new_template_loader.py | 678 ++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 openpype/pipeline/workfile/new_template_loader.py diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py new file mode 100644 index 0000000000..82cb2d9974 --- /dev/null +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -0,0 +1,678 @@ +import os +import collections +from abc import ABCMeta, abstractmethod + +import six + +from openpype.client import get_asset_by_name +from openpype.settings import get_project_settings +from openpype.host import HostBase +from openpype.lib import Logger, StringTemplate, filter_profiles +from openpype.pipeline import legacy_io, Anatomy +from openpype.pipeline.load import get_loaders_by_name +from openpype.pipeline.create import get_legacy_creator_by_name + +from .build_template_exceptions import ( + TemplateProfileNotFound, + TemplateLoadingFailed, + TemplateNotFound, +) + + +@six.add_metaclass(ABCMeta) +class AbstractTemplateLoader: + """Abstraction of Template Loader. + + Args: + host (Union[HostBase, ModuleType]): Implementation of host. + """ + + _log = None + + def __init__(self, host): + # Store host + self._host = host + if isinstance(host, HostBase): + host_name = host.name + else: + host_name = os.environ.get("AVALON_APP") + self._host_name = host_name + + # Shared data across placeholder plugins + self._shared_data = {} + + # Where created objects of placeholder plugins will be stored + self._placeholder_plugins = None + + project_name = legacy_io.active_project() + asset_name = legacy_io.Session["AVALON_ASSET"] + + self.current_asset = asset_name + self.project_name = project_name + self.task_name = legacy_io.Session["AVALON_TASK"] + self.current_asset_doc = get_asset_by_name(project_name, asset_name) + self.task_type = ( + self.current_asset_doc + .get("data", {}) + .get("tasks", {}) + .get(self.task_name, {}) + .get("type") + ) + + @abstractmethod + def get_placeholder_plugin_classes(self): + """Get placeholder plugin classes that can be used to build template. + + Returns: + List[PlaceholderPlugin]: Plugin classes available for host. + """ + + return [] + + @property + def host(self): + """Access to host implementation. + + Returns: + Union[HostBase, ModuleType]: Implementation of host. + """ + + return self._host + + @property + def host_name(self): + """Name of 'host' implementation. + + Returns: + str: Host's name. + """ + + return self._host_name + + @property + def log(self): + """Dynamically created logger for the plugin.""" + + if self._log is None: + self._log = Logger.get_logger(repr(self)) + return self._log + + def refresh(self): + """Reset cached data.""" + + self._placeholder_plugins = None + self._loaders_by_name = None + self._creators_by_name = None + self.clear_shared_data() + + def clear_shared_data(self): + """Clear shared data. + + Method only clear shared data to default state. + """ + + self._shared_data = {} + + def get_loaders_by_name(self): + if self._loaders_by_name is None: + self._loaders_by_name = get_loaders_by_name() + return self._loaders_by_name + + def get_creators_by_name(self): + if self._creators_by_name is None: + self._creators_by_name = get_legacy_creator_by_name() + return self._creators_by_name + + def get_shared_data(self, key): + """Receive shared data across plugins and placeholders. + + This can be used to scroll scene only once to look for placeholder + items if the storing is unified but each placeholder plugin would have + to call it again. + + Shared data are cleaned up on specific callbacks. + + Args: + key (str): Key under which are shared data stored. + + Returns: + Union[None, Any]: None if key was not set. + """ + + return self._shared_data.get(key) + + def set_shared_data(self, key, value): + """Store share data across plugins and placeholders. + + Store data that can be afterwards accessed from any future call. It + is good practice to check if the same value is not already stored under + different key or if the key is not already used for something else. + + Key should be self explanatory to content. + - wrong: 'asset' + - good: 'asset_name' + + Shared data are cleaned up on specific callbacks. + + Args: + key (str): Key under which is key stored. + value (Any): Value that should be stored under the key. + """ + + self._shared_data[key] = value + + @property + def placeholder_plugins(self): + """Access to initialized placeholder plugins. + + Returns: + List[PlaceholderPlugin]: Initialized plugins available for host. + """ + + if self._placeholder_plugins is None: + placeholder_plugins = {} + for cls in self.get_placeholder_plugin_classes(): + try: + plugin = cls(self) + placeholder_plugins[plugin.identifier] = plugin + + except Exception: + self.log.warning( + "Failed to initialize placeholder plugin {}".format( + cls.__name__ + ) + ) + + self._placeholder_plugins = placeholder_plugins + return self._placeholder_plugins + + def get_placeholders(self): + """Collect placeholder items from scene. + + Each placeholder plugin can collect it's placeholders and return them. + This method does not use cached values but always go through the scene. + + Returns: + List[PlaceholderItem]: Sorted placeholder items. + """ + + placeholders = [] + for placeholder_plugin in self.placeholder_plugins: + result = placeholder_plugin.collect_placeholders() + if result: + placeholders.extend(result) + + return list(sorted( + placeholders, + key=lambda i: i.order + )) + + @abstractmethod + def import_template(self, template_path): + """ + Import template in current host. + + Should load the content of template into scene so + 'process_scene_placeholders' can be started. + + Args: + template_path (str): Fullpath for current task and + host's template file. + """ + + pass + + # def template_already_imported(self, err_msg): + # pass + # + # def template_loading_failed(self, err_msg): + # pass + + def _prepare_placeholders(self, placeholders): + """Run preparation part for placeholders on plugins. + + Args: + placeholders (List[PlaceholderItem]): Placeholder items that will + be processed. + """ + + # Prepare placeholder items by plugin + plugins_by_identifier = {} + placeholders_by_plugin_id = collections.defaultdict(list) + for placeholder in placeholders: + plugin = placeholder.plugin + identifier = plugin.identifier + plugins_by_identifier[identifier] = plugin + placeholders_by_plugin_id[identifier].append(placeholder) + + # Plugin should prepare data for passed placeholders + for identifier, placeholders in placeholders_by_plugin_id.items(): + plugin = plugins_by_identifier[identifier] + plugin.prepare_placeholders(placeholders) + + def process_scene_placeholders(self, level_limit=None): + """Find placeholders in scene using plugins and process them. + + This should happen after 'import_template'. + + Collect available placeholders from scene. All of them are processed + after that shared data are cleared. Placeholder items are collected + again and if there are any new the loop happens again. This is possible + to change with defying 'level_limit'. + + Placeholders are marked as processed so they're not re-processed. To + identify which placeholders were already processed is used + placeholder's 'scene_identifier'. + + Args: + level_limit (int): Level of loops that can happen. By default + if is possible to have infinite nested placeholder processing. + """ + + if not self.placeholder_plugins: + self.log.warning("There are no placeholder plugins available.") + return + + placeholders = self.get_placeholders() + if not placeholders: + self.log.warning("No placeholders were found.") + return + + placeholder_by_scene_id = { + placeholder.identifier: placeholder + for placeholder in placeholders + } + all_processed = len(placeholders) == 0 + iter_counter = 0 + while not all_processed: + filtered_placeholders = [] + for placeholder in placeholders: + if placeholder.finished: + continue + + if placeholder.in_progress: + self.log.warning(( + "Placeholder that should be processed" + " is already in progress." + )) + continue + filtered_placeholders.append(placeholder) + + self._prepare_placeholders(filtered_placeholders) + + for placeholder in filtered_placeholders: + placeholder.set_in_progress() + placeholder_plugin = placeholder.plugin + try: + placeholder_plugin.process_placeholder(placeholder) + + except Exception as exc: + placeholder.set_error(exc) + + else: + placeholder.set_finished() + + # Clear shared data before getting new placeholders + self.clear_shared_data() + + if level_limit: + iter_counter += 1 + if iter_counter >= level_limit: + break + + all_processed = True + collected_placeholders = self.get_placeholders() + for placeholder in collected_placeholders: + if placeholder.identifier in placeholder_by_scene_id: + continue + + all_processed = False + identifier = placeholder.identifier + placeholder_by_scene_id[identifier] = placeholder + placeholders.append(placeholder) + + def _get_build_profiles(self): + project_settings = get_project_settings(self.project_name) + return ( + project_settings + [self.host_name] + ["templated_workfile_build"] + ["profiles"] + ) + + def get_template_path(self): + project_name = self.project_name + host_name = self.host_name + task_name = self.task_name + task_type = self.task_type + + build_profiles = self._get_build_profiles() + profile = filter_profiles( + build_profiles, + { + "task_types": task_type, + "task_names": task_name + } + ) + + if not profile: + raise TemplateProfileNotFound(( + "No matching profile found for task '{}' of type '{}' " + "with host '{}'" + ).format(task_name, task_type, host_name)) + + path = profile["path"] + if not path: + raise TemplateLoadingFailed(( + "Template path is not set.\n" + "Path need to be set in {}\\Template Workfile Build " + "Settings\\Profiles" + ).format(host_name.title())) + + # Try fill path with environments and anatomy roots + anatomy = Anatomy(project_name) + fill_data = { + key: value + for key, value in os.environ.items() + } + fill_data["root"] = anatomy.roots + result = StringTemplate.format_template(path, fill_data) + if result.solved: + path = result.normalized() + + if path and os.path.exists(path): + self.log.info("Found template at: '{}'".format(path)) + return path + + solved_path = None + while True: + try: + solved_path = anatomy.path_remapper(path) + except KeyError as missing_key: + raise KeyError( + "Could not solve key '{}' in template path '{}'".format( + missing_key, path)) + + if solved_path is None: + solved_path = path + if solved_path == path: + break + path = solved_path + + solved_path = os.path.normpath(solved_path) + if not os.path.exists(solved_path): + raise TemplateNotFound( + "Template found in openPype settings for task '{}' with host " + "'{}' does not exists. (Not found : {})".format( + task_name, host_name, solved_path)) + + self.log.info("Found template at: '{}'".format(solved_path)) + + return solved_path + + +@six.add_metaclass(ABCMeta) +class PlaceholderPlugin(object): + label = None + placeholder_options = [] + _log = None + + def __init__(self, builder): + self._builder = builder + + @property + def builder(self): + """Access to builder which initialized the plugin. + + Returns: + AbstractTemplateLoader: Loader of template build. + """ + + return self._builder + + @property + def log(self): + """Dynamically created logger for the plugin.""" + + if self._log is None: + self._log = Logger.get_logger(repr(self)) + return self._log + + @property + def identifier(self): + """Identifier which will be stored to placeholder. + + Default implementation uses class name. + + Returns: + str: Unique identifier of placeholder plugin. + """ + + return self.__class__.__name__ + + @abstractmethod + def collect_placeholders(self): + """Collect placeholders from scene. + + Returns: + List[PlaceholderItem]: Placeholder objects. + """ + + pass + + def get_placeholder_options(self): + """Placeholder options for data showed. + + Returns: + List[AbtractAttrDef]: Attribute definitions of placeholder options. + """ + + return self.placeholder_options + + def prepare_placeholders(self, placeholders): + """Preparation part of placeholders. + + Args: + placeholders (List[PlaceholderItem]): List of placeholders that + will be processed. + """ + + pass + + @abstractmethod + def process_placeholder(self, placeholder): + """Process single placeholder item. + + Processing of placeholders is defined by their order thus can't be + processed in batch. + + Args: + placeholder (PlaceholderItem): Placeholder that should be + processed. + """ + + pass + + def cleanup_placeholders(self, placeholders): + """Cleanup of placeholders after processing. + + Not: + Passed placeholders can be failed. + + Args: + placeholders (List[PlaceholderItem]): List of placeholders that + were be processed. + """ + + pass + + def get_plugin_shared_data(self, key): + """Receive shared data across plugin and placeholders. + + Using shared data from builder but stored under plugin identifier. + + Shared data are cleaned up on specific callbacks. + + Args: + key (str): Key under which are shared data stored. + + Returns: + Union[None, Any]: None if key was not set. + """ + + plugin_data = self.builder.get_shared_data(self.identifier) + if plugin_data is None: + return None + return plugin_data.get(key) + + def set_plugin_shared_data(self, key, value): + """Store share data across plugin and placeholders. + + Using shared data from builder but stored under plugin identifier. + + Key should be self explanatory to content. + - wrong: 'asset' + - good: 'asset_name' + + Shared data are cleaned up on specific callbacks. + + Args: + key (str): Key under which is key stored. + value (Any): Value that should be stored under the key. + """ + + plugin_data = self.builder.get_shared_data(self.identifier) + if plugin_data is None: + plugin_data = {} + plugin_data[key] = value + self.builder.set_shared_data(self.identifier, plugin_data) + + +class PlaceholderItem(object): + """Item representing single item in scene that is a placeholder to process. + + Scene identifier is used to avoid processing of the palceholder item + multiple times. + + Args: + scene_identifier (str): Unique scene identifier. If placeholder is + created from the same "node" it must have same identifier. + data (Dict[str, Any]): Data related to placeholder. They're defined + by plugin. + plugin (PlaceholderPlugin): Plugin which created the placeholder item. + """ + + default_order = 100 + + def __init__(self, scene_identifier, data, plugin): + self._log = None + self._scene_identifier = scene_identifier + self._data = data + self._plugin = plugin + + # Keep track about state of Placeholder process + self._state = 0 + + # Exception which happened during processing + self._error = None + + @property + def plugin(self): + """Access to plugin which created placeholder. + + Returns: + PlaceholderPlugin: Plugin object. + """ + + return self._plugin + + @property + def builder(self): + """Access to builder. + + Returns: + AbstractTemplateLoader: Builder which is the top part of + placeholder. + """ + + return self.plugin.builder + + @property + def data(self): + """Placeholder data which can modify how placeholder is processed. + + Possible general keys + - order: Can define the order in which is palceholder processed. + Lower == earlier. + + Other keys are defined by placeholder and should validate them on item + creation. + + Returns: + Dict[str, Any]: Placeholder item data. + """ + + return self._data + + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(repr(self)) + return self._log + + def __repr__(self): + return "< {} {} >".format(self.__class__.__name__, self.name) + + @property + def order(self): + order = self._data.get("order") + if order is None: + return self.default_order + return order + + @property + def scene_identifier(self): + return self._scene_identifier + + @property + def finished(self): + """Item was already processed.""" + + return self._state == 2 + + @property + def in_progress(self): + """Processing is in progress.""" + + return self._state == 1 + + @property + def failed(self): + """Processing of placeholder failed.""" + + return self._error is not None + + @property + def error(self): + """Exception with which the placeholder process failed. + + Gives ability to access the exception. + """ + + return self._error + + def set_in_progress(self): + """Change to in progress state.""" + + self._state = 1 + + def set_finished(self): + """Change to finished state.""" + + self._state = 2 + + def set_error(self, error): + """Set placeholder item as failed and mark it as finished.""" + + self._error = error + self.set_finished() From ae88578dbda9eca89cc792cec498d19c7ef3d6af Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Sep 2022 14:38:45 +0200 Subject: [PATCH 229/513] OP-3682 - changed md5 to sha256 Updated tests. Removed test cli method --- distribution/addon_distribution.py | 28 ++++------ distribution/file_handler.py | 54 ++++++++++++++----- .../tests/test_addon_distributtion.py | 8 +-- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index 95d0b5e397..0e3c672915 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -5,6 +5,7 @@ import attr import logging import requests import platform +import shutil from distribution.file_handler import RemoteFileHandler @@ -87,7 +88,9 @@ class AddonDownloader: """ if not os.path.exists(addon_path): raise ValueError(f"{addon_path} doesn't exist.") - if addon_hash != RemoteFileHandler.calculate_md5(addon_path): + if not RemoteFileHandler.check_integrity(addon_path, + addon_hash, + hash_type="sha256"): raise ValueError(f"{addon_path} doesn't match expected hash.") @classmethod @@ -144,14 +147,14 @@ def get_addons_info(server_endpoint): # "version": "1.0.0", # "addon_url": "c:/projects/openpype_slack_1.0.0.zip", # "type": UrlType.FILESYSTEM, - # "hash": "4f6b8568eb9dd6f510fd7c4dcb676788"}) # noqa + # "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658"}) # noqa # # http_addon = AddonInfo( # **{"name": "openpype_slack", # "version": "1.0.0", # "addon_url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing", # noqa # "type": UrlType.HTTP, - # "hash": "4f6b8568eb9dd6f510fd7c4dcb676788"}) # noqa + # "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658"}) # noqa response = requests.get(server_endpoint) if not response.ok: @@ -205,6 +208,9 @@ def update_addon_state(addon_infos, destination_folder, factory, except Exception: log.warning(f"Error happened during updating {addon.name}", exc_info=True) + if os.path.isdir(addon_dest): + log.debug(f"Cleaning {addon_dest}") + shutil.rmtree(addon_dest) return download_states @@ -228,17 +234,5 @@ def check_addons(server_endpoint, addon_folder, downloaders): raise RuntimeError(f"Unable to update some addons {result}") -def cli(args): - addon_folder = "c:/projects/testing_addons/pypeclub/openpype/addons" - - downloader_factory = AddonDownloader() - downloader_factory.register_format(UrlType.FILESYSTEM, OSAddonDownloader) - downloader_factory.register_format(UrlType.HTTP, HTTPAddonDownloader) - - test_endpoint = "https://34e99f0f-f987-4715-95e6-d2d88caa7586.mock.pstmn.io/get_addons_info" # noqa - if os.environ.get("OPENPYPE_SERVER"): # TODO or from keychain - server_endpoint = os.environ.get("OPENPYPE_SERVER") + "get_addons_info" - else: - server_endpoint = test_endpoint - - check_addons(server_endpoint, addon_folder, downloader_factory) +def cli(*args): + raise NotImplemented \ No newline at end of file diff --git a/distribution/file_handler.py b/distribution/file_handler.py index 8c8b4230ce..f585c77632 100644 --- a/distribution/file_handler.py +++ b/distribution/file_handler.py @@ -33,17 +33,45 @@ class RemoteFileHandler: return md5 == RemoteFileHandler.calculate_md5(fpath, **kwargs) @staticmethod - def check_integrity(fpath, md5=None): + def calculate_sha256(fpath): + """Calculate sha256 for content of the file. + + Args: + fpath (str): Path to file. + + Returns: + str: hex encoded sha256 + + """ + h = hashlib.sha256() + b = bytearray(128 * 1024) + mv = memoryview(b) + with open(fpath, 'rb', buffering=0) as f: + for n in iter(lambda: f.readinto(mv), 0): + h.update(mv[:n]) + return h.hexdigest() + + @staticmethod + def check_sha256(fpath, sha256, **kwargs): + return sha256 == RemoteFileHandler.calculate_sha256(fpath, **kwargs) + + @staticmethod + def check_integrity(fpath, hash_value=None, hash_type=None): if not os.path.isfile(fpath): return False - if md5 is None: + if hash_value is None: return True - return RemoteFileHandler.check_md5(fpath, md5) + if not hash_type: + raise ValueError("Provide hash type, md5 or sha256") + if hash_type == 'md5': + return RemoteFileHandler.check_md5(fpath, hash_value) + if hash_type == "sha256": + return RemoteFileHandler.check_sha256(fpath, hash_value) @staticmethod def download_url( url, root, filename=None, - md5=None, max_redirect_hops=3 + sha256=None, max_redirect_hops=3 ): """Download a file from a url and place it in root. Args: @@ -51,7 +79,7 @@ class RemoteFileHandler: root (str): Directory to place downloaded file in filename (str, optional): Name to save the file under. If None, use the basename of the URL - md5 (str, optional): MD5 checksum of the download. + sha256 (str, optional): sha256 checksum of the download. If None, do not check max_redirect_hops (int, optional): Maximum number of redirect hops allowed @@ -64,7 +92,8 @@ class RemoteFileHandler: os.makedirs(root, exist_ok=True) # check if file is already present locally - if RemoteFileHandler.check_integrity(fpath, md5): + if RemoteFileHandler.check_integrity(fpath, + sha256, hash_type="sha256"): print('Using downloaded and verified file: ' + fpath) return @@ -76,7 +105,7 @@ class RemoteFileHandler: file_id = RemoteFileHandler._get_google_drive_file_id(url) if file_id is not None: return RemoteFileHandler.download_file_from_google_drive( - file_id, root, filename, md5) + file_id, root, filename, sha256) # download the file try: @@ -92,20 +121,21 @@ class RemoteFileHandler: raise e # check integrity of downloaded file - if not RemoteFileHandler.check_integrity(fpath, md5): + if not RemoteFileHandler.check_integrity(fpath, + sha256, hash_type="sha256"): raise RuntimeError("File not found or corrupted.") @staticmethod def download_file_from_google_drive(file_id, root, filename=None, - md5=None): + sha256=None): """Download a Google Drive file from and place it in root. Args: file_id (str): id of file to be downloaded root (str): Directory to place downloaded file in filename (str, optional): Name to save the file under. If None, use the id of the file. - md5 (str, optional): MD5 checksum of the download. + sha256 (str, optional): sha256 checksum of the download. If None, do not check """ # Based on https://stackoverflow.com/questions/38511444/python-download-files-from-google-drive-using-url # noqa @@ -119,8 +149,8 @@ class RemoteFileHandler: os.makedirs(root, exist_ok=True) - if os.path.isfile(fpath) and RemoteFileHandler.check_integrity(fpath, - md5): + if os.path.isfile(fpath) and RemoteFileHandler.check_integrity( + fpath, sha256, hash_type="sha256"): print('Using downloaded and verified file: ' + fpath) else: session = requests.Session() diff --git a/distribution/tests/test_addon_distributtion.py b/distribution/tests/test_addon_distributtion.py index e67ca3c479..717ef1330e 100644 --- a/distribution/tests/test_addon_distributtion.py +++ b/distribution/tests/test_addon_distributtion.py @@ -51,7 +51,7 @@ def sample_addon_info(): } } ], - "hash": "4f6b8568eb9dd6f510fd7c4dcb676788" + "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658" } yield addon_info @@ -109,13 +109,13 @@ def test_update_addon_state(printer, sample_addon_info, addon_info.hash = "brokenhash" result = update_addon_state([addon_info], temp_folder, addon_downloader) assert result["openpype_slack_1.0.0"] == UpdateState.FAILED.value, \ - "Hashes not matching" + "Update should failed because of wrong hash" addon_info.hash = orig_hash result = update_addon_state([addon_info], temp_folder, addon_downloader) assert result["openpype_slack_1.0.0"] == UpdateState.UPDATED.value, \ - "Failed updating" + "Addon should have been updated" result = update_addon_state([addon_info], temp_folder, addon_downloader) assert result["openpype_slack_1.0.0"] == UpdateState.EXISTS.value, \ - "Tried to update" + "Addon should already exist" From b2999a7bbd402154570140fd1db72f6a62158d60 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Sep 2022 14:46:12 +0200 Subject: [PATCH 230/513] OP-3682 - Hound --- distribution/addon_distribution.py | 2 +- distribution/tests/test_addon_distributtion.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/addon_distribution.py b/distribution/addon_distribution.py index 0e3c672915..389b92b10b 100644 --- a/distribution/addon_distribution.py +++ b/distribution/addon_distribution.py @@ -235,4 +235,4 @@ def check_addons(server_endpoint, addon_folder, downloaders): def cli(*args): - raise NotImplemented \ No newline at end of file + raise NotImplemented diff --git a/distribution/tests/test_addon_distributtion.py b/distribution/tests/test_addon_distributtion.py index 717ef1330e..c6ecaca3c8 100644 --- a/distribution/tests/test_addon_distributtion.py +++ b/distribution/tests/test_addon_distributtion.py @@ -51,7 +51,7 @@ def sample_addon_info(): } } ], - "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658" + "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658" # noqa } yield addon_info From 57aa1e6659ab9552c1980dbebcd8b64535469f39 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Sep 2022 14:49:55 +0200 Subject: [PATCH 231/513] implementing not retimed working frame range and retimed handles switch --- openpype/hosts/flame/api/plugin.py | 10 ++++++ .../flame/plugins/create/create_shot_clip.py | 16 ++++++++++ .../publish/collect_timeline_instances.py | 4 +++ .../publish/extract_subset_resources.py | 32 +++++++++++++------ .../publish/collect_otio_frame_ranges.py | 6 ++++ 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index efbabb6a55..145b1f0921 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -361,6 +361,8 @@ class PublishableClip: index_from_segment_default = False use_shot_name_default = False include_handles_default = False + retimed_handles_default = True + retimed_framerange_default = True def __init__(self, segment, **kwargs): self.rename_index = kwargs["rename_index"] @@ -496,6 +498,14 @@ class PublishableClip: "audio", {}).get("value") or False self.include_handles = self.ui_inputs.get( "includeHandles", {}).get("value") or self.include_handles_default + self.retimed_handles = ( + self.ui_inputs.get("retimedHandles", {}).get("value") + or self.retimed_handles_default + ) + self.retimed_framerange = ( + self.ui_inputs.get("retimedFramerange", {}).get("value") + or self.retimed_framerange_default + ) # build subset name from layer name if self.subset_name == "[ track name ]": diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index fa239ea420..b03a39a7ca 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -276,6 +276,22 @@ class CreateShotClip(opfapi.Creator): "target": "tag", "toolTip": "By default handles are excluded", # noqa "order": 3 + }, + "retimedHandles": { + "value": True, + "type": "QCheckBox", + "label": "Retimed handles", + "target": "tag", + "toolTip": "By default handles are retimed.", # noqa + "order": 4 + }, + "retimedFramerange": { + "value": True, + "type": "QCheckBox", + "label": "Retimed framerange", + "target": "tag", + "toolTip": "By default framerange is retimed.", # noqa + "order": 5 } } } diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 992db62c75..d6ff13b059 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -131,6 +131,10 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): "fps": self.fps, "workfileFrameStart": workfile_start, "sourceFirstFrame": int(first_frame), + "notRetimedHandles": ( + not marker_data.get("retimedHandles")), + "notRetimedFramerange": ( + not marker_data.get("retimedFramerange")), "path": file_path, "flameAddTasks": self.add_tasks, "tasks": { diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 3e1e8db986..1af6b00654 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -90,26 +90,38 @@ class ExtractSubsetResources(openpype.api.Extractor): handle_end = instance.data["handleEnd"] handles = max(handle_start, handle_end) include_handles = instance.data.get("includeHandles") + retimed_handles = instance.data.get("retimedHandles") # get media source range with handles source_start_handles = instance.data["sourceStartH"] source_end_handles = instance.data["sourceEndH"] - # retime if needed + + # retime if needed if r_speed != 1.0: - source_start_handles = ( - instance.data["sourceStart"] - r_handle_start) - source_end_handles = ( - source_start_handles - + (r_source_dur - 1) - + r_handle_start - + r_handle_end - ) + if retimed_handles: + # handles are retimed + source_start_handles = ( + instance.data["sourceStart"] - r_handle_start) + source_end_handles = ( + source_start_handles + + (r_source_dur - 1) + + r_handle_start + + r_handle_end + ) + else: + # handles are not retimed + source_end_handles = ( + source_start_handles + + (r_source_dur - 1) + + handle_start + + handle_end + ) # get frame range with handles for representation range frame_start_handle = frame_start - handle_start repre_frame_start = frame_start_handle if include_handles: - if r_speed == 1.0: + if r_speed == 1.0 or not retimed_handles: frame_start_handle = frame_start else: frame_start_handle = ( diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index 40e89e29bc..40a3fa6978 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -29,6 +29,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): # get basic variables otio_clip = instance.data["otioClip"] workfile_start = instance.data["workfileFrameStart"] + not_retime_framerange = instance.data.get("notRetimedFramerange") # get ranges otio_tl_range = otio_clip.range_in_parent() @@ -54,6 +55,11 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): frame_end = frame_start + otio.opentime.to_frames( otio_tl_range.duration, otio_tl_range.duration.rate) - 1 + # in case of retimed clip and frame range should not be retimed + if not_retime_framerange: + frame_end = frame_start + otio.opentime.to_frames( + otio_src_range.duration, otio_src_range.duration.rate) - 1 + data = { "frameStart": frame_start, "frameEnd": frame_end, From d4eeabad7e0883bb2f2d31e0b8479e4b547ba7cc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Sep 2022 21:17:49 +0800 Subject: [PATCH 232/513] adding and loading maya mel workspace through openpype project setting --- openpype/hosts/maya/api/pipeline.py | 6 +++--- openpype/hosts/maya/lib.py | 5 +++-- .../schemas/projects_schema/schema_project_maya.json | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 4578d6fb39..6012d82263 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -59,7 +59,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): self._op_events = {} def install(self): - project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + project_settings = get_project_settings(legacy_io.active_project()) # process path mapping dirmap_processor = MayaDirmap("maya", project_settings) dirmap_processor.process_dirmap() @@ -536,7 +536,7 @@ def on_task_changed(): lib.update_content_on_context_change() msg = " project: {}\n asset: {}\n task:{}".format( - legacy_io.Session["AVALON_PROJECT"], + legacy_io.active_project(), legacy_io.Session["AVALON_ASSET"], legacy_io.Session["AVALON_TASK"] ) @@ -548,7 +548,7 @@ def on_task_changed(): def before_workfile_save(event): - project_name = os.getenv("AVALON_PROJECT") + project_name = legacy_io.active_project() workdir_path = event["workdir_path"] if workdir_path: create_workspace_mel(workdir_path, project_name) diff --git a/openpype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py index 443bf7d10e..e466850810 100644 --- a/openpype/hosts/maya/lib.py +++ b/openpype/hosts/maya/lib.py @@ -1,6 +1,6 @@ import os from openpype.settings import get_project_settings - +from openpype.api import Logger def create_workspace_mel(workdir, project_name): dst_filepath = os.path.join(workdir, "workspace.mel") @@ -15,7 +15,8 @@ def create_workspace_mel(workdir, project_name): # Skip if mel script in settings is empty if not mel_script: - return + log = Logger.get_logger("create_workspace_mel") + log.debug("File 'workspace.mel' not created. Settings value is empty.") with open(dst_filepath, "w") as mel_file: mel_file.write(mel_script) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index a54f8e6e4f..72c974642f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -8,6 +8,7 @@ { "type": "text", "multiline" : true, + "use_label_wrap": true, "key": "mel_workspace", "label": "Maya MEL Workspace" }, From a6d7df1423fdd5ec37744a34042e01a76776f263 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Sep 2022 21:19:15 +0800 Subject: [PATCH 233/513] adding and loading maya mel workspace through openpype project setting --- openpype/hosts/maya/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py index e466850810..e07e174dd6 100644 --- a/openpype/hosts/maya/lib.py +++ b/openpype/hosts/maya/lib.py @@ -2,6 +2,7 @@ import os from openpype.settings import get_project_settings from openpype.api import Logger + def create_workspace_mel(workdir, project_name): dst_filepath = os.path.join(workdir, "workspace.mel") if os.path.exists(dst_filepath): From b7256e7c19ba376e438a88ca5d0b4a9609a44423 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Sep 2022 21:49:02 +0800 Subject: [PATCH 234/513] adding and loading maya mel workspace through openpype project setting --- openpype/hosts/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py index e07e174dd6..6f7bb8f986 100644 --- a/openpype/hosts/maya/lib.py +++ b/openpype/hosts/maya/lib.py @@ -1,6 +1,6 @@ import os from openpype.settings import get_project_settings -from openpype.api import Logger +from openpype.lib import Logger def create_workspace_mel(workdir, project_name): From d2b3c80fb93acf26f9d61a6164dadf2941cd7930 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Sep 2022 16:02:38 +0200 Subject: [PATCH 235/513] improving variable name --- openpype/plugins/publish/collect_otio_frame_ranges.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index 40a3fa6978..cfb0318950 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -29,7 +29,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): # get basic variables otio_clip = instance.data["otioClip"] workfile_start = instance.data["workfileFrameStart"] - not_retime_framerange = instance.data.get("notRetimedFramerange") + workfile_source_duration = instance.data.get("notRetimedFramerange") # get ranges otio_tl_range = otio_clip.range_in_parent() @@ -56,7 +56,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): otio_tl_range.duration, otio_tl_range.duration.rate) - 1 # in case of retimed clip and frame range should not be retimed - if not_retime_framerange: + if workfile_source_duration: frame_end = frame_start + otio.opentime.to_frames( otio_src_range.duration, otio_src_range.duration.rate) - 1 From 9bcd86bac7ca0294fc41bc3d2465166b3b5e8861 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Sep 2022 22:12:16 +0800 Subject: [PATCH 236/513] load and edit mel workspace within the Openpype project settings --- openpype/hosts/maya/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py index 6f7bb8f986..ffb2f0b27c 100644 --- a/openpype/hosts/maya/lib.py +++ b/openpype/hosts/maya/lib.py @@ -18,6 +18,7 @@ def create_workspace_mel(workdir, project_name): if not mel_script: log = Logger.get_logger("create_workspace_mel") log.debug("File 'workspace.mel' not created. Settings value is empty.") + return with open(dst_filepath, "w") as mel_file: mel_file.write(mel_script) From 0d6c40bb32fff14cb08cc33505acf298555b95e7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 7 Sep 2022 19:13:00 +0200 Subject: [PATCH 237/513] added few missing abstract methods and attributes --- .../pipeline/workfile/new_template_loader.py | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 82cb2d9974..4b77168aa1 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -1,5 +1,6 @@ import os import collections +import copy from abc import ABCMeta, abstractmethod import six @@ -43,6 +44,8 @@ class AbstractTemplateLoader: # Where created objects of placeholder plugins will be stored self._placeholder_plugins = None + self._loaders_by_name = None + self._creators_by_name = None project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] @@ -180,12 +183,29 @@ class AbstractTemplateLoader: self.log.warning( "Failed to initialize placeholder plugin {}".format( cls.__name__ - ) + ), + exc_info=True ) self._placeholder_plugins = placeholder_plugins return self._placeholder_plugins + def create_placeholder(self, plugin_identifier, placeholder_data): + """Create new placeholder using plugin identifier and data. + + Args: + plugin_identifier (str): Identifier of plugin. That's how builder + know which plugin should be used. + placeholder_data (Dict[str, Any]): Placeholder item data. They + should match options required by the plugin. + + Returns: + PlaceholderItem: Created placeholder item. + """ + + plugin = self.placeholder_plugins[plugin_identifier] + return plugin.create_placeholder(placeholder_data) + def get_placeholders(self): """Collect placeholder items from scene. @@ -197,7 +217,7 @@ class AbstractTemplateLoader: """ placeholders = [] - for placeholder_plugin in self.placeholder_plugins: + for placeholder_plugin in self.placeholder_plugins.values(): result = placeholder_plugin.collect_placeholders() if result: placeholders.extend(result) @@ -450,6 +470,42 @@ class PlaceholderPlugin(object): return self.__class__.__name__ + @abstractmethod + def create_placeholder(self, placeholder_data): + """Create new placeholder in scene and get it's item. + + It matters on the plugin implementation if placeholder will use + selection in scene or create new node. + + Args: + placeholder_data (Dict[str, Any]): Data that were created + based on attribute definitions from 'get_placeholder_options'. + + Returns: + PlaceholderItem: Created placeholder item. + """ + + pass + + @abstractmethod + def update_placeholder(self, placeholder_item, placeholder_data): + """Update placeholder item with new data. + + New data should be propagated to object of placeholder item itself + and also into the scene. + + Reason: + Some placeholder plugins may require some special way how the + updates should be propagated to object. + + Args: + placeholder_item (PlaceholderItem): Object of placeholder that + should be updated. + placeholder_data (Dict[str, Any]): Data related to placeholder. + Should match plugin options. + """ + pass + @abstractmethod def collect_placeholders(self): """Collect placeholders from scene. @@ -614,6 +670,14 @@ class PlaceholderItem(object): return self._data + def to_dict(self): + """Create copy of item's data. + + Returns: + Dict[str, Any]: Placeholder data. + """ + return copy.deepcopy(self.data) + @property def log(self): if self._log is None: From 5627c8ec627e4157e45eaeeb5a58e12216979f7c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 11:23:56 +0200 Subject: [PATCH 238/513] enabled pixmap scaling in tray --- openpype/tools/tray/pype_tray.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 85bc00ead6..c32a074fd1 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -778,6 +778,14 @@ def main(): if not app: app = QtWidgets.QApplication([]) + for attr_name in ( + "AA_EnableHighDpiScaling", + "AA_UseHighDpiPixmaps" + ): + attr = getattr(QtCore.Qt, attr_name, None) + if attr is not None: + app.setAttribute(attr) + starter = PypeTrayStarter(app) # TODO remove when pype.exe will have an icon From 4f3accee1ac84991543493a32768c1aa99672035 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 11:34:33 +0200 Subject: [PATCH 239/513] change "hierarchy" key to "nodesHierarchy" in maya --- openpype/hosts/maya/plugins/publish/collect_assembly.py | 2 +- openpype/hosts/maya/plugins/publish/extract_assembly.py | 2 +- .../hosts/maya/plugins/publish/validate_assembly_transforms.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_assembly.py b/openpype/hosts/maya/plugins/publish/collect_assembly.py index 1a65bf1fde..2aef9ab908 100644 --- a/openpype/hosts/maya/plugins/publish/collect_assembly.py +++ b/openpype/hosts/maya/plugins/publish/collect_assembly.py @@ -70,7 +70,7 @@ class CollectAssembly(pyblish.api.InstancePlugin): data[representation_id].append(instance_data) instance.data["scenedata"] = dict(data) - instance.data["hierarchy"] = list(set(hierarchy_nodes)) + instance.data["nodesHierarchy"] = list(set(hierarchy_nodes)) def get_file_rule(self, rule): return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) diff --git a/openpype/hosts/maya/plugins/publish/extract_assembly.py b/openpype/hosts/maya/plugins/publish/extract_assembly.py index 482930b76e..120805894e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_assembly.py +++ b/openpype/hosts/maya/plugins/publish/extract_assembly.py @@ -33,7 +33,7 @@ class ExtractAssembly(openpype.api.Extractor): json.dump(instance.data["scenedata"], filepath, ensure_ascii=False) self.log.info("Extracting point cache ..") - cmds.select(instance.data["hierarchy"]) + cmds.select(instance.data["nodesHierarchy"]) # Run basic alembic exporter extract_alembic(file=hierarchy_path, diff --git a/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py b/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py index f793846555..fb25b617be 100644 --- a/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py @@ -48,7 +48,7 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): from openpype.hosts.maya.api import lib # Get all transforms in the loaded containers - container_roots = cmds.listRelatives(instance.data["hierarchy"], + container_roots = cmds.listRelatives(instance.data["nodesHierarchy"], children=True, type="transform", fullPath=True) From 1c7f32e93a750cd3f97b17fb3fba53054a6e74f5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Sep 2022 17:43:01 +0800 Subject: [PATCH 240/513] adding lock task workfiles when users are working on them --- openpype/hosts/maya/api/pipeline.py | 99 ++++++++++++++++++++- openpype/pipeline/workfile/lock_workfile.py | 67 ++++++++++++++ openpype/tools/workfiles/files_widget.py | 15 ++++ 3 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 openpype/pipeline/workfile/lock_workfile.py diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index c963b5d996..b645b41fa0 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -31,6 +31,12 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.load import any_outdated_containers +from openpype.pipeline.workfile.lock_workfile import ( + create_workfile_lock, + get_username, + remove_workfile_lock, + is_workfile_locked +) from openpype.hosts.maya import MAYA_ROOT_DIR from openpype.hosts.maya.lib import copy_workspace_mel @@ -99,7 +105,10 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): register_event_callback("open", on_open) register_event_callback("new", on_new) register_event_callback("before.save", on_before_save) + register_event_callback("before.close", on_before_close) + register_event_callback("before.file.open", before_file_open) register_event_callback("taskChanged", on_task_changed) + register_event_callback("workfile.open.before", before_workfile_open) register_event_callback("workfile.save.before", before_workfile_save) def open_workfile(self, filepath): @@ -161,8 +170,25 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): ) ) - self._op_events[_on_scene_open] = OpenMaya.MSceneMessage.addCallback( - OpenMaya.MSceneMessage.kAfterOpen, _on_scene_open + self._op_events[_on_scene_open] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterOpen, + _on_scene_open + ) + ) + + self._op_events[_before_scene_open] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kBeforeOpen, + _before_scene_open + ) + ) + + self._op_events[_before_close_maya] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kMayaExiting, + _before_close_maya + ) ) self.log.info("Installed event handler _on_scene_save..") @@ -170,6 +196,8 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): self.log.info("Installed event handler _on_scene_new..") self.log.info("Installed event handler _on_maya_initialized..") self.log.info("Installed event handler _on_scene_open..") + self.log.info("Installed event handler _check_lock_file..") + self.log.info("Installed event handler _before_close_maya..") def _set_project(): @@ -216,6 +244,14 @@ def _on_scene_open(*args): emit_event("open") +def _before_close_maya(*args): + emit_event("before.close") + + +def _before_scene_open(*args): + emit_event("before.file.open") + + def _before_scene_save(return_code, client_data): # Default to allowing the action. Registered @@ -229,6 +265,12 @@ def _before_scene_save(return_code, client_data): ) +def _remove_workfile_lock(): + filepath = current_file() + if filepath: + remove_workfile_lock(filepath) + + def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) pyblish.api.deregister_host("mayabatch") @@ -426,6 +468,49 @@ def on_before_save(): return lib.validate_fps() +def after_file_open(): + """Check if there is a user opening the file""" + log.info("Running callback on checking the lock file...") + + # add the lock file when opening the file + filepath = current_file() + + if not is_workfile_locked(filepath): + create_workfile_lock(filepath) + + else: + username = get_username(filepath) + reminder = cmds.window(title="Reminder", width=400, height=30) + cmds.columnLayout(adjustableColumn=True) + cmds.separator() + cmds.columnLayout(adjustableColumn=True) + comment = " %s is working the same workfile!" % username + cmds.text(comment, align='center') + cmds.text(vis=False) + cmds.rowColumnLayout(numberOfColumns=3, + columnWidth=[(1, 300), (2, 100)], + columnSpacing=[(2, 10)]) + cmds.separator(vis=False) + quit_command = "cmds.quit(force=True);cmds.deleteUI('%s')" % reminder + cmds.button(label='Ok', command=quit_command) + cmds.showWindow(reminder) + + +def on_before_close(): + """Delete the lock file after user quitting the Maya Scene""" + log.info("Closing Maya...") + # delete the lock file + filepath = current_file() + remove_workfile_lock(filepath) + + +def before_file_open(): + """check lock file when the file changed""" + log.info("check lock file when file changed...") + # delete the lock file + _remove_workfile_lock() + + def on_save(): """Automatically add IDs to new nodes @@ -434,6 +519,8 @@ def on_save(): """ log.info("Running callback on save..") + # remove lockfile if users jumps over from one scene to another + _remove_workfile_lock() # # Update current task for the current scene # update_task_from_path(cmds.file(query=True, sceneName=True)) @@ -491,6 +578,9 @@ def on_open(): dialog.on_clicked.connect(_on_show_inventory) dialog.show() + # create lock file for the maya scene + after_file_open() + def on_new(): """Set project resolution and fps when create a new file""" @@ -544,7 +634,12 @@ def on_task_changed(): ) +def before_workfile_open(): + _remove_workfile_lock() + + def before_workfile_save(event): + _remove_workfile_lock() workdir_path = event["workdir_path"] if workdir_path: copy_workspace_mel(workdir_path) diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py new file mode 100644 index 0000000000..03dee66d46 --- /dev/null +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -0,0 +1,67 @@ +import os +import json +from uuid import uuid4 +from openpype.lib.pype_info import get_workstation_info + + +def _read_lock_file(lock_filepath): + with open(lock_filepath, "r") as stream: + data = json.load(stream) + return data + + +def _get_lock_file(filepath): + return filepath + ".lock" + + +def is_workfile_locked(filepath): + lock_filepath = _get_lock_file(filepath) + if not os.path.exists(lock_filepath): + return False + return True + + +def is_workfile_locked_for_current_process(filepath): + if not is_workfile_locked(): + return False + + lock_filepath = _get_lock_file(filepath) + process_id = os.environ["OPENPYPE_PROCESS_ID"] + data = _read_lock_file(lock_filepath) + return data["process_id"] == process_id + + +def delete_workfile_lock(filepath): + lock_filepath = _get_lock_file(filepath) + if not os.path.exists(lock_filepath): + return + + if is_workfile_locked_for_current_process(filepath): + os.remove(filepath) + + +def create_workfile_lock(filepath): + lock_filepath = _get_lock_file(filepath) + process_id = os.environ.get("OPENPYPE_PROCESS_ID") + if not process_id: + process_id = str(uuid4()) + os.environ["OPENPYPE_PROCESS_ID"] = process_id + info = get_workstation_info() + info["process_id"] = process_id + with open(lock_filepath, "w") as stream: + json.dump(info, stream) + + +def get_username(filepath): + lock_filepath = _get_lock_file(filepath) + with open(lock_filepath, "r") as stream: + data = json.load(stream) + username = data["username"] + return username + + +def remove_workfile_lock(filepath): + lock_filepath = _get_lock_file(filepath) + if not os.path.exists(lock_filepath): + return + return os.remove(lock_filepath) \ No newline at end of file diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index a5d5b14bb6..6a554efd8b 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -8,6 +8,10 @@ from Qt import QtWidgets, QtCore from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id +from openpype.pipeline.workfile.lock_workfile import ( + is_workfile_locked, + get_username +) from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -454,6 +458,17 @@ class FilesWidget(QtWidgets.QWidget): def open_file(self, filepath): host = self.host + if is_workfile_locked(filepath): + username = get_username(filepath) + popup_dialog = QtWidgets.QMessageBox(parent=self) + popup_dialog.setWindowTitle("Warning") + popup_dialog.setText(username + " is using the file") + popup_dialog.setStandardButtons(popup_dialog.Ok) + + result = popup_dialog.exec_() + if result == popup_dialog.Ok: + return False + if isinstance(host, IWorkfileHost): has_unsaved_changes = host.workfile_has_unsaved_changes() else: From 3782ad4782b2044c20ead45c13a6f457d9a332e5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Sep 2022 17:47:31 +0800 Subject: [PATCH 241/513] adding lock task workfiles when users are working on them --- openpype/pipeline/workfile/lock_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index 03dee66d46..8e75f6fb61 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -64,4 +64,4 @@ def remove_workfile_lock(filepath): lock_filepath = _get_lock_file(filepath) if not os.path.exists(lock_filepath): return - return os.remove(lock_filepath) \ No newline at end of file + return os.remove(lock_filepath) From 01d3b0b0b0e2ed72b4ff509d4e0685f44086ef85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 13:30:27 +0200 Subject: [PATCH 242/513] add logs about missing attributes --- openpype/tools/tray/pype_tray.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index c32a074fd1..348573a191 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -9,11 +9,11 @@ import platform from Qt import QtCore, QtGui, QtWidgets import openpype.version -from openpype.api import ( - resources, - get_system_settings +from openpype import resources, style +from openpype.lib import ( + get_openpype_execute_args, + Logger, ) -from openpype.lib import get_openpype_execute_args, Logger from openpype.lib.openpype_version import ( op_version_control_available, get_expected_version, @@ -25,8 +25,8 @@ from openpype.lib.openpype_version import ( get_openpype_version, ) from openpype.modules import TrayModulesManager -from openpype import style from openpype.settings import ( + get_system_settings, SystemSettings, ProjectSettings, DefaultsNotDefined @@ -774,6 +774,7 @@ class PypeTrayStarter(QtCore.QObject): def main(): + log = Logger.get_logger(__name__) app = QtWidgets.QApplication.instance() if not app: app = QtWidgets.QApplication([]) @@ -783,7 +784,12 @@ def main(): "AA_UseHighDpiPixmaps" ): attr = getattr(QtCore.Qt, attr_name, None) - if attr is not None: + if attr is None: + log.debug(( + "Missing QtCore.Qt attribute \"{}\"." + " UI quality may be affected." + ).format(attr_name)) + else: app.setAttribute(attr) starter = PypeTrayStarter(app) From d7a768f718944c9322ae30e9167ea64b655be5e5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Sep 2022 15:51:24 +0200 Subject: [PATCH 243/513] turn plugin off by default --- openpype/settings/defaults/project_settings/global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9258343440..99a2e16a7c 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -4,7 +4,7 @@ "follow_workfile_version": false }, "CollectAudio": { - "enabled": true, + "enabled": false, "audio_subset_name": "audioMain" }, "CollectSceneVersion": { From 49dff63f08207eea0218cf37e4824795d08e3895 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 17:50:55 +0200 Subject: [PATCH 244/513] Fix detection of workfile instance --- openpype/modules/deadline/abstract_submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index f698b7688e..512ff800ee 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -519,7 +519,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): instance = self._instance workfile_instance = self._get_workfile_instance(instance.context) - if not workfile_instance: + if workfile_instance is None: return # determine published path from Anatomy. From 50a9a7973b32a3fb38b1f42861288ffb44ed823f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 18:31:15 +0200 Subject: [PATCH 245/513] small modifications of placeholder identifiers and errors --- .../pipeline/workfile/new_template_loader.py | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 4b77168aa1..b1231c2308 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -299,7 +299,7 @@ class AbstractTemplateLoader: return placeholder_by_scene_id = { - placeholder.identifier: placeholder + placeholder.scene_identifier: placeholder for placeholder in placeholders } all_processed = len(placeholders) == 0 @@ -343,11 +343,11 @@ class AbstractTemplateLoader: all_processed = True collected_placeholders = self.get_placeholders() for placeholder in collected_placeholders: - if placeholder.identifier in placeholder_by_scene_id: + identifier = placeholder.scene_identifier + if identifier in placeholder_by_scene_id: continue all_processed = False - identifier = placeholder.identifier placeholder_by_scene_id[identifier] = placeholder placeholders.append(placeholder) @@ -434,7 +434,6 @@ class AbstractTemplateLoader: @six.add_metaclass(ABCMeta) class PlaceholderPlugin(object): label = None - placeholder_options = [] _log = None def __init__(self, builder): @@ -516,14 +515,14 @@ class PlaceholderPlugin(object): pass - def get_placeholder_options(self): + def get_placeholder_options(self, options=None): """Placeholder options for data showed. Returns: List[AbtractAttrDef]: Attribute definitions of placeholder options. """ - return self.placeholder_options + return [] def prepare_placeholders(self, placeholders): """Preparation part of placeholders. @@ -629,8 +628,9 @@ class PlaceholderItem(object): # Keep track about state of Placeholder process self._state = 0 - # Exception which happened during processing - self._error = None + # Error messages to be shown in UI + # - all other messages should be logged + self._errors = [] # -> List[str] @property def plugin(self): @@ -676,6 +676,7 @@ class PlaceholderItem(object): Returns: Dict[str, Any]: Placeholder data. """ + return copy.deepcopy(self.data) @property @@ -710,21 +711,6 @@ class PlaceholderItem(object): return self._state == 1 - @property - def failed(self): - """Processing of placeholder failed.""" - - return self._error is not None - - @property - def error(self): - """Exception with which the placeholder process failed. - - Gives ability to access the exception. - """ - - return self._error - def set_in_progress(self): """Change to in progress state.""" @@ -735,8 +721,15 @@ class PlaceholderItem(object): self._state = 2 - def set_error(self, error): + def add_error(self, error): """Set placeholder item as failed and mark it as finished.""" - self._error = error - self.set_finished() + self._errors.append(error) + + def get_errors(self): + """Exception with which the placeholder process failed. + + Gives ability to access the exception. + """ + + return self._errors From 2eef3a8f826b614b1f496d6aa05b02da6a328a02 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 18:31:39 +0200 Subject: [PATCH 246/513] initial commit of maya implementation of new template builder --- .../hosts/maya/api/new_template_builder.py | 505 ++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 openpype/hosts/maya/api/new_template_builder.py diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py new file mode 100644 index 0000000000..023f240061 --- /dev/null +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -0,0 +1,505 @@ +import re +from maya import cmds + +from openpype.client import get_representations +from openpype.lib import attribute_definitions +from openpype.pipeline import legacy_io +from openpype.pipeline.workfile.build_template_exceptions import ( + TemplateAlreadyImported +) +from openpype.pipeline.workfile.new_template_loader import ( + AbstractTemplateLoader, + PlaceholderPlugin, + PlaceholderItem, +) + +from .lib import read, imprint + +PLACEHOLDER_SET = "PLACEHOLDERS_SET" + + +class MayaTemplateLoader(AbstractTemplateLoader): + """Concrete implementation of AbstractTemplateLoader for maya""" + + def import_template(self, path): + """Import template into current scene. + Block if a template is already loaded. + + Args: + path (str): A path to current template (usually given by + get_template_path implementation) + + Returns: + bool: Wether the template was succesfully imported or not + """ + + if cmds.objExists(PLACEHOLDER_SET): + raise TemplateAlreadyImported(( + "Build template already loaded\n" + "Clean scene if needed (File > New Scene)" + )) + + cmds.sets(name=PLACEHOLDER_SET, empty=True) + cmds.file(path, i=True, returnNewNodes=True) + + cmds.setAttr(PLACEHOLDER_SET + '.hiddenInOutliner', True) + + # This should be handled by creators + # for set_name in cmds.listSets(allSets=True): + # if ( + # cmds.objExists(set_name) + # and cmds.attributeQuery('id', node=set_name, exists=True) + # and cmds.getAttr(set_name + '.id') == 'pyblish.avalon.instance' + # ): + # if cmds.attributeQuery('asset', node=set_name, exists=True): + # cmds.setAttr( + # set_name + '.asset', + # legacy_io.Session['AVALON_ASSET'], type='string' + # ) + + return True + + def get_placeholder_plugin_classes(self): + return [ + MayaLoadPlaceholderPlugin + ] + + # def template_already_imported(self, err_msg): + # clearButton = "Clear scene and build" + # updateButton = "Update template" + # abortButton = "Abort" + # + # title = "Scene already builded" + # message = ( + # "It's seems a template was already build for this scene.\n" + # "Error message reveived :\n\n\"{}\"".format(err_msg)) + # buttons = [clearButton, updateButton, abortButton] + # defaultButton = clearButton + # cancelButton = abortButton + # dismissString = abortButton + # answer = cmds.confirmDialog( + # t=title, + # m=message, + # b=buttons, + # db=defaultButton, + # cb=cancelButton, + # ds=dismissString) + # + # if answer == clearButton: + # cmds.file(newFile=True, force=True) + # self.import_template(self.template_path) + # self.populate_template() + # elif answer == updateButton: + # self.update_missing_containers() + # elif answer == abortButton: + # return + + # def get_loaded_containers_by_id(self): + # try: + # containers = cmds.sets("AVALON_CONTAINERS", q=True) + # except ValueError: + # return None + # + # return [ + # cmds.getAttr(container + '.representation') + # for container in containers] + + +class MayaLoadPlaceholderPlugin(PlaceholderPlugin): + identifier = "maya.load" + label = "Maya load" + + def _collect_scene_placeholders(self): + # Cache placeholder data to shared data + placeholder_nodes = self.builder.get_shared_data("placeholder_nodes") + if placeholder_nodes is None: + attributes = cmds.ls("*.plugin_identifier", long=True) + placeholder_nodes = [ + self._parse_placeholder_node_data(attribute.rpartition(".")[0]) + for attribute in attributes + ] + self.builder.set_shared_data( + "placeholder_nodes", placeholder_nodes + ) + return placeholder_nodes + + def _parse_placeholder_node_data(self, node_name): + placeholder_data = read(node_name) + parent_name = ( + cmds.getAttr(node_name + ".parent", asString=True) + or node_name.rpartition("|")[0] + or "" + ) + if parent_name: + siblings = cmds.listRelatives(parent_name, children=True) + else: + siblings = cmds.ls(assemblies=True) + node_shortname = node_name.rpartition("|")[2] + current_index = cmds.getAttr(node_name + ".index", asString=True) + if current_index < 0: + current_index = siblings.index(node_shortname) + + placeholder_data.update({ + "parent": parent_name, + "index": current_index + }) + return placeholder_data + + def _create_placeholder_name(self, placeholder_data): + # TODO implement placeholder name logic + return "Placeholder" + + def create_placeholder(self, placeholder_data): + selection = cmds.ls(selection=True) + if not selection: + raise ValueError("Nothing is selected") + if len(selection) > 1: + raise ValueError("More then one item are selected") + + placeholder_data["plugin_identifier"] = self.identifier + + placeholder_name = self._create_placeholder_name(placeholder_data) + + placeholder = cmds.spaceLocator(name=placeholder_name)[0] + + # TODO: this can crash if selection can't be used + cmds.parent(placeholder, selection[0]) + + # get the long name of the placeholder (with the groups) + placeholder_full_name = ( + cmds.ls(selection[0], long=True)[0] + + "|" + + placeholder.replace("|", "") + ) + + imprint(placeholder_full_name, placeholder_data) + + # Add helper attributes to keep placeholder info + cmds.addAttr( + placeholder_full_name, + longName="parent", + hidden=True, + dataType="string" + ) + cmds.addAttr( + placeholder_full_name, + longName="index", + hidden=True, + attributeType="short", + defaultValue=-1 + ) + + cmds.setAttr(placeholder_full_name + ".parent", "", type="string") + + def update_placeholder(self, placeholder_item, placeholder_data): + node_name = placeholder_item.scene_identifier + new_values = {} + for key, value in placeholder_data.items(): + placeholder_value = placeholder_item.data.get(key) + if value != placeholder_value: + new_values[key] = value + placeholder_item.data[key] = value + + imprint(node_name, new_values) + + def collect_placeholders(self): + filtered_placeholders = [] + for placeholder_data in self._collect_scene_placeholders(): + if placeholder_data.get("plugin_identifier") != self.identifier: + continue + + filtered_placeholders.append(placeholder_data) + + output = [] + for placeholder_data in filtered_placeholders: + # TODO do data validations and maybe updgrades if are invalid + output.append(LoadPlaceholder(placeholder_data)) + return output + + def process_placeholder(self, placeholder): + current_asset_doc = self.current_asset_doc + linked_assets = self.linked_assets + loader_name = placeholder.data["loader"] + loader_args = placeholder.data["loader_args"] + + # TODO check loader existence + placeholder_representations = placeholder.get_representations( + current_asset_doc, + linked_assets + ) + + if not placeholder_representations: + self.log.info(( + "There's no representation for this placeholder: {}" + ).format(placeholder.scene_identifier)) + return + + loaders_by_name = self.builder.get_loaders_by_name() + for representation in placeholder_representations: + repre_context = representation["context"] + self.log.info( + "Loading {} from {} with loader {}\n" + "Loader arguments used : {}".format( + repre_context["subset"], + repre_context["asset"], + loader_name, + loader_args + ) + ) + try: + container = self.load( + placeholder, loaders_by_name, representation) + except Exception: + placeholder.load_failed(representation) + + else: + placeholder.load_succeed(container) + # TODO find out if 'postload make sense?' + # finally: + # self.postload(placeholder) + + def get_placeholder_options(self, options=None): + loaders_by_name = self.builder.get_loaders_by_name() + loader_names = list(sorted(loaders_by_name.keys())) + options = options or {} + return [ + attribute_definitions.UISeparatorDef(), + attribute_definitions.UILabelDef("Main attributes"), + + attribute_definitions.EnumDef( + "builder_type", + label="Asset Builder Type", + default=options.get("builder_type"), + items=[ + ("context_asset", "Current asset"), + ("linked_asset", "Linked assets"), + ("all_assets", "All assets") + ], + tooltip=( + "Asset Builder Type\n" + "\nBuilder type describe what template loader will look" + " for." + "\ncontext_asset : Template loader will look for subsets" + " of current context asset (Asset bob will find asset)" + "\nlinked_asset : Template loader will look for assets" + " linked to current context asset." + "\nLinked asset are looked in database under" + " field \"inputLinks\"" + ) + ), + attribute_definitions.TextDef( + "family", + label="Family", + default=options.get("family"), + placeholder="model, look, ..." + ), + attribute_definitions.TextDef( + "representation", + label="Representation name", + default=options.get("representation"), + placeholder="ma, abc, ..." + ), + attribute_definitions.EnumDef( + "loader", + label="Loader", + default=options.get("loader"), + items=[ + (loader_name, loader_name) + for loader_name in loader_names + ], + tooltip="""Loader +Defines what OpenPype loader will be used to load assets. +Useable loader depends on current host's loader list. +Field is case sensitive. +""" + ), + attribute_definitions.TextDef( + "loader_args", + label="Loader Arguments", + default=options.get("loader_args"), + placeholder='{"camera":"persp", "lights":True}', + tooltip="""Loader +Defines a dictionnary of arguments used to load assets. +Useable arguments depend on current placeholder Loader. +Field should be a valid python dict. Anything else will be ignored. +""" + ), + attribute_definitions.NumberDef( + "order", + label="Order", + default=options.get("order") or 0, + decimals=0, + minimum=0, + maximum=999, + tooltip="""Order +Order defines asset loading priority (0 to 999) +Priority rule is : "lowest is first to load".""" + ), + attribute_definitions.UISeparatorDef(), + attribute_definitions.UILabelDef("Optional attributes"), + attribute_definitions.UISeparatorDef(), + attribute_definitions.TextDef( + "asset", + label="Asset filter", + default=options.get("asset"), + placeholder="regex filtering by asset name", + tooltip=( + "Filtering assets by matching field regex to asset's name" + ) + ), + attribute_definitions.TextDef( + "subset", + label="Subset filter", + default=options.get("subset"), + placeholder="regex filtering by subset name", + tooltip=( + "Filtering assets by matching field regex to subset's name" + ) + ), + attribute_definitions.TextDef( + "hierarchy", + label="Hierarchy filter", + default=options.get("hierarchy"), + placeholder="regex filtering by asset's hierarchy", + tooltip=( + "Filtering assets by matching field asset's hierarchy" + ) + ) + ] + + +class LoadPlaceholder(PlaceholderItem): + """Concrete implementation of AbstractPlaceholder for maya + """ + + def __init__(self, *args, **kwargs): + super(LoadPlaceholder, self).__init__(*args, **kwargs) + self._failed_representations = [] + + def parent_in_hierarchy(self, container): + """Parent loaded container to placeholder's parent. + + ie : Set loaded content as placeholder's sibling + + Args: + container (str): Placeholder loaded containers + """ + + if not container: + return + + roots = cmds.sets(container, q=True) + nodes_to_parent = [] + for root in roots: + if root.endswith("_RN"): + refRoot = cmds.referenceQuery(root, n=True)[0] + refRoot = cmds.listRelatives(refRoot, parent=True) or [refRoot] + nodes_to_parent.extend(refRoot) + elif root not in cmds.listSets(allSets=True): + nodes_to_parent.append(root) + + elif not cmds.sets(root, q=True): + return + + if self.data['parent']: + cmds.parent(nodes_to_parent, self.data['parent']) + # Move loaded nodes to correct index in outliner hierarchy + placeholder_form = cmds.xform( + self._scene_identifier, + q=True, + matrix=True, + worldSpace=True + ) + for node in set(nodes_to_parent): + cmds.reorder(node, front=True) + cmds.reorder(node, relative=self.data['index']) + cmds.xform(node, matrix=placeholder_form, ws=True) + + holding_sets = cmds.listSets(object=self._scene_identifier) + if not holding_sets: + return + for holding_set in holding_sets: + cmds.sets(roots, forceElement=holding_set) + + def clean(self): + """Hide placeholder, parent them to root + add them to placeholder set and register placeholder's parent + to keep placeholder info available for future use + """ + + node = self._scene_identifier + if self.data['parent']: + cmds.setAttr(node + '.parent', self.data['parent'], type='string') + if cmds.getAttr(node + '.index') < 0: + cmds.setAttr(node + '.index', self.data['index']) + + holding_sets = cmds.listSets(object=node) + if holding_sets: + for set in holding_sets: + cmds.sets(node, remove=set) + + if cmds.listRelatives(node, p=True): + node = cmds.parent(node, world=True)[0] + cmds.sets(node, addElement=PLACEHOLDER_SET) + cmds.hide(node) + cmds.setAttr(node + '.hiddenInOutliner', True) + + def get_representations(self, current_asset_doc, linked_asset_docs): + project_name = legacy_io.active_project() + + builder_type = self.data["builder_type"] + if builder_type == "context_asset": + context_filters = { + "asset": [current_asset_doc["name"]], + "subset": [re.compile(self.data["subset"])], + "hierarchy": [re.compile(self.data["hierarchy"])], + "representations": [self.data["representation"]], + "family": [self.data["family"]] + } + + elif builder_type != "linked_asset": + context_filters = { + "asset": [re.compile(self.data["asset"])], + "subset": [re.compile(self.data["subset"])], + "hierarchy": [re.compile(self.data["hierarchy"])], + "representation": [self.data["representation"]], + "family": [self.data["family"]] + } + + else: + asset_regex = re.compile(self.data["asset"]) + linked_asset_names = [] + for asset_doc in linked_asset_docs: + asset_name = asset_doc["name"] + if asset_regex.match(asset_name): + linked_asset_names.append(asset_name) + + context_filters = { + "asset": linked_asset_names, + "subset": [re.compile(self.data["subset"])], + "hierarchy": [re.compile(self.data["hierarchy"])], + "representation": [self.data["representation"]], + "family": [self.data["family"]], + } + + return list(get_representations( + project_name, + context_filters=context_filters + )) + + def get_errors(self): + if not self._failed_representations: + return [] + message = ( + "Failed to load {} representations using Loader {}" + ).format( + len(self._failed_representations), + self.data["loader"] + ) + return [message] + + def load_failed(self, representation): + self._failed_representations.append(representation) + + def load_succeed(self, container): + self.parent_in_hierarchy(container) From 5fe6d2606ddc69e265f89584326ee19939c4b2c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Sep 2022 18:32:20 +0200 Subject: [PATCH 247/513] added simple toolt to be able show attribute definitionis for workfile template builder --- .../tools/workfile_template_build/__init__.py | 5 + .../tools/workfile_template_build/window.py | 232 ++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 openpype/tools/workfile_template_build/__init__.py create mode 100644 openpype/tools/workfile_template_build/window.py diff --git a/openpype/tools/workfile_template_build/__init__.py b/openpype/tools/workfile_template_build/__init__.py new file mode 100644 index 0000000000..70b8867759 --- /dev/null +++ b/openpype/tools/workfile_template_build/__init__.py @@ -0,0 +1,5 @@ +from .window import WorkfileBuildDialog + +__all__ = ( + "WorkfileBuildDialog", +) diff --git a/openpype/tools/workfile_template_build/window.py b/openpype/tools/workfile_template_build/window.py new file mode 100644 index 0000000000..a5cec465ec --- /dev/null +++ b/openpype/tools/workfile_template_build/window.py @@ -0,0 +1,232 @@ +from Qt import QtWidgets + +from openpype import style +from openpype.lib import Logger +from openpype.pipeline import legacy_io +from openpype.widgets.attribute_defs import AttributeDefinitionsWidget + + +class WorkfileBuildDialog(QtWidgets.QDialog): + def __init__(self, host, builder, parent=None): + super(WorkfileBuildDialog, self).__init__(parent) + self.setWindowTitle("Workfile Placeholder Manager") + + self._log = None + + self._first_show = True + self._first_refreshed = False + + self._builder = builder + self._host = host + # Mode can be 0 (create) or 1 (update) + # TODO write it a little bit better + self._mode = 0 + self._update_item = None + self._last_selected_plugin = None + + host_name = getattr(self._host, "name", None) + if not host_name: + host_name = legacy_io.Session.get("AVALON_APP") or "NA" + self._host_name = host_name + + plugins_combo = QtWidgets.QComboBox(self) + + content_widget = QtWidgets.QWidget(self) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + + btns_widget = QtWidgets.QWidget(self) + create_btn = QtWidgets.QPushButton("Create", btns_widget) + save_btn = QtWidgets.QPushButton("Save", btns_widget) + close_btn = QtWidgets.QPushButton("Close", btns_widget) + + create_btn.setVisible(False) + save_btn.setVisible(False) + + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + btns_layout.addStretch(1) + btns_layout.addWidget(create_btn, 0) + btns_layout.addWidget(save_btn, 0) + btns_layout.addWidget(close_btn, 0) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(plugins_combo, 0) + main_layout.addWidget(content_widget, 1) + main_layout.addWidget(btns_widget, 0) + + create_btn.clicked.connect(self._on_create_click) + save_btn.clicked.connect(self._on_save_click) + close_btn.clicked.connect(self._on_close_click) + plugins_combo.currentIndexChanged.connect(self._on_plugin_change) + + self._attr_defs_widget = None + self._plugins_combo = plugins_combo + + self._content_widget = content_widget + self._content_layout = content_layout + + self._create_btn = create_btn + self._save_btn = save_btn + self._close_btn = close_btn + + @property + def log(self): + if self._log is None: + self._log = Logger.get_logger(self.__class__.__name__) + return self._log + + def _clear_content_widget(self): + while self._content_layout.count() > 0: + item = self._content_layout.takeAt(0) + widget = item.widget + if widget: + widget.setVisible(False) + widget.deleteLater() + + def _add_message_to_content(self, message): + msg_label = QtWidgets.QLabel(message, self._content_widget) + self._content_layout.addWidget(msg_label, 0) + self._content_layout.addStretch(1) + + def refresh(self): + self._first_refreshed = True + self._clear_content_widget() + + if not self._builder: + self._add_message_to_content(( + "Host \"{}\" does not have implemented logic" + " for template workfile build." + ).format(self._host_name)) + self._update_ui_visibility() + return + + if self._mode == 1: + self._update_ui_visibility() + return + + placeholder_plugins = builder.placeholder_plugins + if not placeholder_plugins: + self._add_message_to_content(( + "Host \"{}\" does not have implemented plugins" + " for template workfile build." + ).format(self._host_name)) + self._update_ui_visibility() + return + + last_selected_plugin = self._last_selected_plugin + self._last_selected_plugin = None + self._plugins_combo.clear() + for identifier, plugin in placeholder_plugins.items(): + label = plugin.label or plugin.identifier + self._plugins_combo.addItem(label, plugin.identifier) + + index = self._plugins_combo.findData(last_selected_plugin) + if index < 0: + index = 0 + self._plugins_combo.setCurrentIndex(index) + self._on_plugin_change() + + self._update_ui_visibility() + + def set_create_mode(self): + if self._mode == 0: + return + + self._mode = 0 + self._update_item = None + self.refresh() + + def set_update_mode(self, update_item): + if self._mode == 1: + return + + self._mode = 1 + self._update_item = update_item + if not update_item: + self._add_message_to_content(( + "Nothing to update." + " (You maybe don't have selected placeholder.)" + )) + else: + self._create_option_widgets( + update_item.plugin, update_item.to_dict() + ) + self._update_ui_visibility() + + def _create_option_widgets(self, plugin, options=None): + self._clear_content_widget() + attr_defs = plugin.get_placeholder_options(options) + widget = AttributeDefinitionsWidget(attr_defs, self._content_widget) + self._content_layout.addWidget(widget, 0) + self._content_layout.addStretch(1) + self._attr_defs_widget = widget + + def _update_ui_visibility(self): + create_mode = self._mode == 0 + self._plugins_combo.setVisible(create_mode) + + if not self._builder: + self._save_btn.setVisible(False) + self._create_btn.setVisible(False) + return + + save_enabled = not create_mode + if save_enabled: + save_enabled = self._update_item is not None + self._save_btn.setVisible(save_enabled) + self._create_btn.setVisible(create_mode) + + def _on_plugin_change(self): + index = self._plugins_combo.currentIndex() + plugin_identifier = self._plugins_combo.itemData(index) + if plugin_identifier == self._last_selected_plugin: + return + + self._last_selected_plugin = plugin_identifier + plugin = self._builder.placeholder_plugins.get(plugin_identifier) + self._create_option_widgets(plugin) + + def _on_save_click(self): + options = self._attr_defs_widget.current_value() + plugin = self._builder.placeholder_plugins.get( + self._last_selected_plugin + ) + # TODO much better error handling + try: + plugin.update_placeholder(self._update_item, options) + self.accept() + except Exception as exc: + self.log.warning("Something went wrong", exc_info=True) + dialog = QtWidgets.QMessageBox(self) + dialog.setWindowTitle("Something went wrong") + dialog.setText("Something went wrong") + dialog.exec_() + + def _on_create_click(self): + options = self._attr_defs_widget.current_value() + plugin = self._builder.placeholder_plugins.get( + self._last_selected_plugin + ) + # TODO much better error handling + try: + plugin.create_placeholder(options) + self.accept() + except Exception as exc: + self.log.warning("Something went wrong", exc_info=True) + dialog = QtWidgets.QMessageBox(self) + dialog.setWindowTitle("Something went wrong") + dialog.setText("Something went wrong") + dialog.exec_() + + def _on_close_click(self): + self.reject() + + def showEvent(self, event): + super(WorkfileBuildDialog, self).showEvent(event) + if not self._first_refreshed: + self.refresh() + + if self._first_show: + self._first_show = False + self.setStyleSheet(style.load_stylesheet()) + self.resize(390, 450) From ff149b68215adc8c0211d7248d94b17376496ad1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 19:42:55 +0200 Subject: [PATCH 248/513] Remove unused import --- openpype/tools/loader/model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 9d1f1e045c..19b135bfc5 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -14,8 +14,7 @@ from openpype.client import ( get_versions, get_hero_versions, get_version_by_name, - get_representations, - get_representations_parents + get_representations ) from openpype.pipeline import ( registered_host, From dc903c752014348bda4fd4a2a05600ab7ec66f87 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 19:49:17 +0200 Subject: [PATCH 249/513] Store `loaded_in_scene` as `bool` in model --- openpype/tools/loader/model.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 19b135bfc5..8543672617 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -679,9 +679,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): data["asset"] = asset_docs_by_id[asset_id]["name"] data["last_version"] = last_version - - loaded = subset_doc["_id"] in subsets_loaded_by_id - data["loaded_in_scene"] = "yes" if loaded else "no" + data["loaded_in_scene"] = subset_doc["_id"] in subsets_loaded_by_id # Sync server data data.update( From 9ebd602a91a21e427cbd063bdd3841a0370d6b22 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 20:06:34 +0200 Subject: [PATCH 250/513] Add delegate to loaded in scene column for "yes/no" and colorized column --- openpype/tools/loader/widgets.py | 7 ++++++- openpype/tools/utils/delegates.py | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 3c4a89aa0f..e27d7e6a12 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -36,7 +36,8 @@ from openpype.tools.utils import ( ) from openpype.tools.utils.delegates import ( VersionDelegate, - PrettyTimeDelegate + PrettyTimeDelegate, + LoadedInSceneDelegate ) from openpype.tools.utils.widgets import ( OptionalMenu, @@ -234,6 +235,10 @@ class SubsetWidget(QtWidgets.QWidget): column = model.Columns.index("repre_info") view.setItemDelegateForColumn(column, avail_delegate) + loaded_in_scene_delegate = LoadedInSceneDelegate(view) + column = model.Columns.index("loaded_in_scene") + view.setItemDelegateForColumn(column, loaded_in_scene_delegate) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index d6c2d69e76..3547251282 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -291,3 +291,30 @@ class PrettyTimeDelegate(QtWidgets.QStyledItemDelegate): def displayText(self, value, locale): if value is not None: return pretty_timestamp(value) + + +class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate): + """Delegate for Loaded in Scene state columns. + + Shows "yes" or "no" for True or False values + Colorizes green or dark grey based on True or False values + + """ + + def __init__(self, *args, **kwargs): + super(LoadedInSceneDelegate, self).__init__(*args, **kwargs) + self._colors = { + True: QtGui.QColor(80, 170, 80), + False: QtGui.QColor(90, 90, 90) + } + + def displayText(self, value, locale): + return "yes" if value else "no" + + def initStyleOption(self, option, index): + super(LoadedInSceneDelegate, self).initStyleOption(option, index) + + # Colorize based on value + value = index.data(QtCore.Qt.DisplayRole) + color = self._colors[bool(value)] + option.palette.setBrush(QtGui.QPalette.Text, color) From 379a5f5d787f3d4fff3f16e3d6d4b1f6adea390e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 22:52:08 +0200 Subject: [PATCH 251/513] Log file format in more human-readable manner instead of an integer --- .../maya/plugins/publish/validate_rendersettings.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index feb6a16dac..679535aa8c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -10,6 +10,12 @@ import openpype.api from openpype.hosts.maya.api import lib +def get_redshift_image_format_labels(): + """Return nice labels for Redshift image formats.""" + var = "$g_redshiftImageFormatLabels" + return mel.eval("{0}={0}".format(var)) + + class ValidateRenderSettings(pyblish.api.InstancePlugin): """Validates the global render settings @@ -191,10 +197,11 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): pass elif default_ext != aov_ext: + labels = get_redshift_image_format_labels() cls.log.error(("AOV file format is not the same " "as the one set globally " - "{} != {}").format(default_ext, - aov_ext)) + "{} != {}").format(labels[default_ext], + labels[aov_ext])) invalid = True if renderer == "renderman": From d37f65e81ff2d88dcdd4bca07048fcea0d85849d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 22:55:36 +0200 Subject: [PATCH 252/513] Fix `prefix` is None issue --- openpype/hosts/maya/plugins/publish/validate_rendersettings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 679535aa8c..eea60ef4f3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -108,8 +108,9 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # Get the node attributes for current renderer attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS['default']) + # Prefix attribute can return None when a value was never set prefix = lib.get_attr_in_layer(cls.ImagePrefixes[renderer], - layer=layer) + layer=layer) or "" padding = lib.get_attr_in_layer("{node}.{padding}".format(**attrs), layer=layer) From 4d84a06bd323425c7c033400853a058c983b4e32 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 22:57:46 +0200 Subject: [PATCH 253/513] Repair defaultRenderGlobals.animation (must be enabled) --- openpype/hosts/maya/plugins/publish/validate_rendersettings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index eea60ef4f3..dcf77ad1f7 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -302,6 +302,9 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): default = lib.RENDER_ATTRS['default'] render_attrs = lib.RENDER_ATTRS.get(renderer, default) + # Repair animation must be enabled + cmds.setAttr("defaultRenderGlobals.animation", True) + # Repair prefix if renderer != "renderman": node = render_attrs["node"] From fea33ee4966a0e7f00792bdf98b73515976a334d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 23:04:41 +0200 Subject: [PATCH 254/513] Clarify log message which of the values is AOV format and which is global --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index dcf77ad1f7..ed4a076302 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -199,10 +199,10 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): elif default_ext != aov_ext: labels = get_redshift_image_format_labels() - cls.log.error(("AOV file format is not the same " - "as the one set globally " - "{} != {}").format(labels[default_ext], - labels[aov_ext])) + cls.log.error( + "AOV file format {} does not match global file format " + "{}".format(labels[default_ext], labels[aov_ext]) + ) invalid = True if renderer == "renderman": From 1f8887eabb2a68c08af178d35f408e6a3eb5acfc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 23:05:03 +0200 Subject: [PATCH 255/513] Cosmetics --- openpype/hosts/maya/plugins/publish/validate_rendersettings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index ed4a076302..7f0985f69b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -337,8 +337,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cmds.optionMenuGrp("vrayRenderElementSeparator", v=instance.data.get("aovSeparator", "_")) cmds.setAttr( - "{}.fileNameRenderElementSeparator".format( - node), + "{}.fileNameRenderElementSeparator".format(node), instance.data.get("aovSeparator", "_"), type="string" ) From 26ae84df161344da8e3132f25d4655a470598645 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Sep 2022 13:20:49 +0800 Subject: [PATCH 256/513] adding lock task workfiles when users are working on them --- openpype/hosts/maya/api/pipeline.py | 60 ++++++++++++------- openpype/pipeline/workfile/lock_workfile.py | 60 ++++++++++++------- .../defaults/project_settings/global.json | 3 +- .../schemas/schema_global_tools.json | 25 ++++++++ openpype/tools/workfiles/files_widget.py | 15 ++++- 5 files changed, 116 insertions(+), 47 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index b645b41fa0..67cf80e707 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -33,9 +33,10 @@ from openpype.pipeline import ( from openpype.pipeline.load import any_outdated_containers from openpype.pipeline.workfile.lock_workfile import ( create_workfile_lock, - get_username, + get_user_from_lock, remove_workfile_lock, - is_workfile_locked + is_workfile_locked, + is_workfile_lock_enabled ) from openpype.hosts.maya import MAYA_ROOT_DIR from openpype.hosts.maya.lib import copy_workspace_mel @@ -266,11 +267,21 @@ def _before_scene_save(return_code, client_data): def _remove_workfile_lock(): + """Remove workfile lock on current file""" + if not handle_workfile_locks(): + return filepath = current_file() if filepath: remove_workfile_lock(filepath) +def handle_workfile_locks(): + if lib.IS_HEADLESS: + return False + project_name = legacy_io.active_project() + return is_workfile_lock_enabled(MayaHost.name, project_name) + + def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) pyblish.api.deregister_host("mayabatch") @@ -468,8 +479,10 @@ def on_before_save(): return lib.validate_fps() -def after_file_open(): +def check_lock_on_current_file(): """Check if there is a user opening the file""" + if not handle_workfile_locks(): + return log.info("Running callback on checking the lock file...") # add the lock file when opening the file @@ -477,23 +490,25 @@ def after_file_open(): if not is_workfile_locked(filepath): create_workfile_lock(filepath) + return - else: - username = get_username(filepath) - reminder = cmds.window(title="Reminder", width=400, height=30) - cmds.columnLayout(adjustableColumn=True) - cmds.separator() - cmds.columnLayout(adjustableColumn=True) - comment = " %s is working the same workfile!" % username - cmds.text(comment, align='center') - cmds.text(vis=False) - cmds.rowColumnLayout(numberOfColumns=3, - columnWidth=[(1, 300), (2, 100)], - columnSpacing=[(2, 10)]) - cmds.separator(vis=False) - quit_command = "cmds.quit(force=True);cmds.deleteUI('%s')" % reminder - cmds.button(label='Ok', command=quit_command) - cmds.showWindow(reminder) + username = get_user_from_lock(filepath) + reminder = cmds.window(title="Reminder", width=400, height=30) + cmds.columnLayout(adjustableColumn=True) + cmds.separator() + cmds.columnLayout(adjustableColumn=True) + comment = " %s is working the same workfile!" % username + cmds.text(comment, align='center') + cmds.text(vis=False) + cmds.rowColumnLayout(numberOfColumns=3, + columnWidth=[(1,200), (2, 100), (3, 100)], + columnSpacing=[(3, 10)]) + cmds.separator(vis=False) + cancel_command = "cmds.file(new=True);cmds.deleteUI('%s')" % reminder + ignore_command ="cmds.deleteUI('%s')" % reminder + cmds.button(label='Cancel', command=cancel_command) + cmds.button(label = "Ignore", command=ignore_command) + cmds.showWindow(reminder) def on_before_close(): @@ -501,12 +516,13 @@ def on_before_close(): log.info("Closing Maya...") # delete the lock file filepath = current_file() - remove_workfile_lock(filepath) + if handle_workfile_locks(): + remove_workfile_lock(filepath) def before_file_open(): """check lock file when the file changed""" - log.info("check lock file when file changed...") + log.info("Removing lock on current file before scene open...") # delete the lock file _remove_workfile_lock() @@ -579,7 +595,7 @@ def on_open(): dialog.show() # create lock file for the maya scene - after_file_open() + check_lock_on_current_file() def on_new(): diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index 8e75f6fb61..b62f80c507 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -1,17 +1,22 @@ import os import json from uuid import uuid4 +from openpype.lib import Logger, filter_profiles from openpype.lib.pype_info import get_workstation_info +from openpype.settings import get_project_settings def _read_lock_file(lock_filepath): + if not os.path.exists(lock_filepath): + log = Logger.get_logger("_read_lock_file") + log.debug("lock file is not created or readable as expected!") with open(lock_filepath, "r") as stream: data = json.load(stream) return data def _get_lock_file(filepath): - return filepath + ".lock" + return filepath + ".oplock" def is_workfile_locked(filepath): @@ -22,46 +27,59 @@ def is_workfile_locked(filepath): def is_workfile_locked_for_current_process(filepath): - if not is_workfile_locked(): + if not is_workfile_locked(filepath): return False lock_filepath = _get_lock_file(filepath) - process_id = os.environ["OPENPYPE_PROCESS_ID"] data = _read_lock_file(lock_filepath) - return data["process_id"] == process_id + return data["process_id"] == _get_process_id() def delete_workfile_lock(filepath): lock_filepath = _get_lock_file(filepath) - if not os.path.exists(lock_filepath): - return - - if is_workfile_locked_for_current_process(filepath): - os.remove(filepath) + if os.path.exists(lock_filepath): + os.remove(lock_filepath) def create_workfile_lock(filepath): lock_filepath = _get_lock_file(filepath) - process_id = os.environ.get("OPENPYPE_PROCESS_ID") - if not process_id: - process_id = str(uuid4()) - os.environ["OPENPYPE_PROCESS_ID"] = process_id info = get_workstation_info() - info["process_id"] = process_id + info["process_id"] = _get_process_id() with open(lock_filepath, "w") as stream: json.dump(info, stream) -def get_username(filepath): +def get_user_from_lock(filepath): lock_filepath = _get_lock_file(filepath) - with open(lock_filepath, "r") as stream: - data = json.load(stream) + if not os.path.exists(lock_filepath): + return + data = _read_lock_file(lock_filepath) username = data["username"] return username def remove_workfile_lock(filepath): - lock_filepath = _get_lock_file(filepath) - if not os.path.exists(lock_filepath): - return - return os.remove(lock_filepath) + if is_workfile_locked_for_current_process(filepath): + delete_workfile_lock(filepath) + + +def _get_process_id(): + process_id = os.environ.get("OPENPYPE_PROCESS_ID") + if not process_id: + process_id = str(uuid4()) + os.environ["OPENPYPE_PROCESS_ID"] = process_id + return process_id + +def is_workfile_lock_enabled(host_name, project_name, project_setting=None): + if project_setting is None: + project_setting = get_project_settings(project_name) + workfile_lock_profiles = ( + project_setting + ["global"] + ["tools"] + ["Workfiles"] + ["workfile_lock_profiles"]) + profile = filter_profiles(workfile_lock_profiles,{"host_name": host_name}) + if not profile: + return False + return profile["enabled"] diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0ff9363ba7..fc98a06ef1 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -403,7 +403,8 @@ "enabled": false } ], - "extra_folders": [] + "extra_folders": [], + "workfile_lock_profiles": [] }, "loader": { "family_filter_profiles": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index f8c9482e5f..d422278667 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -238,6 +238,31 @@ } ] } + }, + { + "type": "list", + "key": "workfile_lock_profiles", + "label": "Workfile lock profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "hosts-enum", + "key": "host_name", + "label": "Hosts", + "multiselection": true + }, + { + "type": "splitter" + }, + { + "key": "enabled", + "label": "Enabled", + "type": "boolean" + } + ] + } } ] }, diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 6a554efd8b..5eab3af144 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -10,7 +10,9 @@ from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id from openpype.pipeline.workfile.lock_workfile import ( is_workfile_locked, - get_username + get_user_from_lock, + is_workfile_lock_enabled, + is_workfile_locked_for_current_process ) from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate @@ -456,10 +458,17 @@ class FilesWidget(QtWidgets.QWidget): "host_name": self.host_name } + def _is_workfile_locked(self, filepath): + if not is_workfile_lock_enabled(self.host_name, self.project_name): + return False + if not is_workfile_locked(filepath): + return False + return not is_workfile_locked_for_current_process(filepath) + def open_file(self, filepath): host = self.host - if is_workfile_locked(filepath): - username = get_username(filepath) + if self._is_workfile_locked(filepath): + username = get_user_from_lock(filepath) popup_dialog = QtWidgets.QMessageBox(parent=self) popup_dialog.setWindowTitle("Warning") popup_dialog.setText(username + " is using the file") From 4978981631933e98aba32fd8ebed363382ac5a06 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Sep 2022 13:25:12 +0800 Subject: [PATCH 257/513] adding lock task workfiles when users are working on them --- openpype/hosts/maya/api/pipeline.py | 2 +- openpype/pipeline/workfile/lock_workfile.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 67cf80e707..355537b92c 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -505,7 +505,7 @@ def check_lock_on_current_file(): columnSpacing=[(3, 10)]) cmds.separator(vis=False) cancel_command = "cmds.file(new=True);cmds.deleteUI('%s')" % reminder - ignore_command ="cmds.deleteUI('%s')" % reminder + ignore_command = "cmds.deleteUI('%s')" % reminder cmds.button(label='Cancel', command=cancel_command) cmds.button(label = "Ignore", command=ignore_command) cmds.showWindow(reminder) diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index b62f80c507..2a7f25e524 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -70,6 +70,7 @@ def _get_process_id(): os.environ["OPENPYPE_PROCESS_ID"] = process_id return process_id + def is_workfile_lock_enabled(host_name, project_name, project_setting=None): if project_setting is None: project_setting = get_project_settings(project_name) From 365a90c3c17c0d65bb6f97b9fd25d8299e3b0c0b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Sep 2022 13:26:58 +0800 Subject: [PATCH 258/513] adding lock task workfiles when users are working on them --- openpype/hosts/maya/api/pipeline.py | 4 ++-- openpype/pipeline/workfile/lock_workfile.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 355537b92c..b34a216c13 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -501,13 +501,13 @@ def check_lock_on_current_file(): cmds.text(comment, align='center') cmds.text(vis=False) cmds.rowColumnLayout(numberOfColumns=3, - columnWidth=[(1,200), (2, 100), (3, 100)], + columnWidth=[(1, 200), (2, 100), (3, 100)], columnSpacing=[(3, 10)]) cmds.separator(vis=False) cancel_command = "cmds.file(new=True);cmds.deleteUI('%s')" % reminder ignore_command = "cmds.deleteUI('%s')" % reminder cmds.button(label='Cancel', command=cancel_command) - cmds.button(label = "Ignore", command=ignore_command) + cmds.button(label="Ignore", command=ignore_command) cmds.showWindow(reminder) diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index 2a7f25e524..7c8c4a8066 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -80,7 +80,7 @@ def is_workfile_lock_enabled(host_name, project_name, project_setting=None): ["tools"] ["Workfiles"] ["workfile_lock_profiles"]) - profile = filter_profiles(workfile_lock_profiles,{"host_name": host_name}) + profile = filter_profiles(workfile_lock_profiles, {"host_name": host_name}) if not profile: return False return profile["enabled"] From 872d85f91d31f678c28759c05dd35ed388bab8ec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 11:52:33 +0200 Subject: [PATCH 259/513] Remove old legacy plug-ins that are of no use anymore --- .../plugins/publish/collect_maya_scene.py | 25 ------------------- .../hosts/maya/plugins/publish/collect_rig.py | 22 ---------------- 2 files changed, 47 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/collect_maya_scene.py delete mode 100644 openpype/hosts/maya/plugins/publish/collect_rig.py diff --git a/openpype/hosts/maya/plugins/publish/collect_maya_scene.py b/openpype/hosts/maya/plugins/publish/collect_maya_scene.py deleted file mode 100644 index eb21b17989..0000000000 --- a/openpype/hosts/maya/plugins/publish/collect_maya_scene.py +++ /dev/null @@ -1,25 +0,0 @@ -from maya import cmds - -import pyblish.api - - -class CollectMayaScene(pyblish.api.InstancePlugin): - """Collect Maya Scene Data - - """ - - order = pyblish.api.CollectorOrder + 0.2 - label = 'Collect Model Data' - families = ["mayaScene"] - - def process(self, instance): - # Extract only current frame (override) - frame = cmds.currentTime(query=True) - instance.data["frameStart"] = frame - instance.data["frameEnd"] = frame - - # make ftrack publishable - if instance.data.get('families'): - instance.data['families'].append('ftrack') - else: - instance.data['families'] = ['ftrack'] diff --git a/openpype/hosts/maya/plugins/publish/collect_rig.py b/openpype/hosts/maya/plugins/publish/collect_rig.py deleted file mode 100644 index 98ae1e8009..0000000000 --- a/openpype/hosts/maya/plugins/publish/collect_rig.py +++ /dev/null @@ -1,22 +0,0 @@ -from maya import cmds - -import pyblish.api - - -class CollectRigData(pyblish.api.InstancePlugin): - """Collect rig data - - Ensures rigs are published to Ftrack. - - """ - - order = pyblish.api.CollectorOrder + 0.2 - label = 'Collect Rig Data' - families = ["rig"] - - def process(self, instance): - # make ftrack publishable - if instance.data.get('families'): - instance.data['families'].append('ftrack') - else: - instance.data['families'] = ['ftrack'] From 3943d74f3ea3b72d08f2d2e114a30b4a5e00a515 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Sep 2022 11:55:05 +0200 Subject: [PATCH 260/513] flame: adding batch action hook --- openpype/hosts/flame/api/__init__.py | 4 +- openpype/hosts/flame/api/menu.py | 50 +++++++++++++++++++ .../hosts/flame/startup/openpype_in_flame.py | 13 +++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index 76c1c93379..7da91d41e4 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -51,7 +51,8 @@ from .pipeline import ( ) from .menu import ( FlameMenuProjectConnect, - FlameMenuTimeline + FlameMenuTimeline, + FlameMenuBatch ) from .plugin import ( Creator, @@ -131,6 +132,7 @@ __all__ = [ # menu "FlameMenuProjectConnect", "FlameMenuTimeline", + "FlameMenuBatch", # plugin "Creator", diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index 7f1a6a24e2..a822059930 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -201,3 +201,53 @@ class FlameMenuTimeline(_FlameMenuApp): if self.flame: self.flame.execute_shortcut('Rescan Python Hooks') self.log.info('Rescan Python Hooks') + + +class FlameMenuBatch(_FlameMenuApp): + + # flameMenuProjectconnect app takes care of the preferences dialog as well + + def __init__(self, framework): + _FlameMenuApp.__init__(self, framework) + + def __getattr__(self, name): + def method(*args, **kwargs): + project = self.dynamic_menu_data.get(name) + if project: + self.link_project(project) + return method + + def build_menu(self): + if not self.flame: + return [] + + menu = deepcopy(self.menu) + + menu['actions'].append({ + "name": "Load...", + "execute": lambda x: self.tools_helper.show_loader() + }) + menu['actions'].append({ + "name": "Manage...", + "execute": lambda x: self.tools_helper.show_scene_inventory() + }) + menu['actions'].append({ + "name": "Library...", + "execute": lambda x: self.tools_helper.show_library_loader() + }) + return menu + + def refresh(self, *args, **kwargs): + self.rescan() + + def rescan(self, *args, **kwargs): + if not self.flame: + try: + import flame + self.flame = flame + except ImportError: + self.flame = None + + if self.flame: + self.flame.execute_shortcut('Rescan Python Hooks') + self.log.info('Rescan Python Hooks') diff --git a/openpype/hosts/flame/startup/openpype_in_flame.py b/openpype/hosts/flame/startup/openpype_in_flame.py index f2ac23b19e..60f6612b7f 100644 --- a/openpype/hosts/flame/startup/openpype_in_flame.py +++ b/openpype/hosts/flame/startup/openpype_in_flame.py @@ -73,6 +73,8 @@ def load_apps(): opfapi.FlameMenuProjectConnect(opfapi.CTX.app_framework)) opfapi.CTX.flame_apps.append( opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) + opfapi.CTX.flame_apps.append( + opfapi.FlameMenuBatch(opfapi.CTX.app_framework)) opfapi.CTX.app_framework.log.info("Apps are loaded") @@ -191,3 +193,14 @@ def get_timeline_custom_ui_actions(): openpype_install() return _build_app_menu("FlameMenuTimeline") + +def get_batch_custom_ui_actions(): + """Hook to create submenu in batch + + Returns: + list: menu object + """ + # install openpype and the host + openpype_install() + + return _build_app_menu("FlameMenuBatch") \ No newline at end of file From ad9e172e4a0d97be2124aa6816ba18a28008e8cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 12:02:10 +0200 Subject: [PATCH 261/513] Add functionality back in to store playback time range with the mayaScene publish --- .../publish/collect_maya_scene_time.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py diff --git a/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py b/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py new file mode 100644 index 0000000000..71ca785e1d --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py @@ -0,0 +1,26 @@ +from maya import cmds + +import pyblish.api + + +class CollectMayaSceneTime(pyblish.api.InstancePlugin): + """Collect Maya Scene playback range + + This allows to reproduce the playback range for the content to be loaded. + It does *not* limit the extracted data to only data inside that time range. + + """ + + order = pyblish.api.CollectorOrder + 0.2 + label = 'Collect Maya Scene Time' + families = ["mayaScene"] + + def process(self, instance): + instance.data.update({ + "frameStart": cmds.playbackOptions(query=True, minTime=True), + "frameEnd": cmds.playbackOptions(query=True, maxTime=True), + "frameStartHandle": cmds.playbackOptions(query=True, + animationStartTime=True), + "frameEndHandle": cmds.playbackOptions(query=True, + animationEndTime=True), + }) From b7ee7669af0a3f43f1050d24818c2c7550331ba6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 12:03:32 +0200 Subject: [PATCH 262/513] Remove redundant comma --- openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py b/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py index 71ca785e1d..7e198df14d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py +++ b/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py @@ -22,5 +22,5 @@ class CollectMayaSceneTime(pyblish.api.InstancePlugin): "frameStartHandle": cmds.playbackOptions(query=True, animationStartTime=True), "frameEndHandle": cmds.playbackOptions(query=True, - animationEndTime=True), + animationEndTime=True) }) From 7ca64b67c39eff858028f2aa52fca2d3b6de8f90 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:22:00 +0200 Subject: [PATCH 263/513] modified attr defs creation --- .../hosts/maya/api/new_template_builder.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index 023f240061..06d1cf0fd7 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -265,6 +265,7 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): return [ attribute_definitions.UISeparatorDef(), attribute_definitions.UILabelDef("Main attributes"), + attribute_definitions.UISeparatorDef(), attribute_definitions.EnumDef( "builder_type", @@ -307,22 +308,26 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): (loader_name, loader_name) for loader_name in loader_names ], - tooltip="""Loader -Defines what OpenPype loader will be used to load assets. -Useable loader depends on current host's loader list. -Field is case sensitive. -""" + tooltip=( + "Loader" + "\nDefines what OpenPype loader will be used to" + " load assets." + "\nUseable loader depends on current host's loader list." + "\nField is case sensitive." + ) ), attribute_definitions.TextDef( "loader_args", label="Loader Arguments", default=options.get("loader_args"), placeholder='{"camera":"persp", "lights":True}', - tooltip="""Loader -Defines a dictionnary of arguments used to load assets. -Useable arguments depend on current placeholder Loader. -Field should be a valid python dict. Anything else will be ignored. -""" + tooltip=( + "Loader" + "\nDefines a dictionnary of arguments used to load assets." + "\nUseable arguments depend on current placeholder Loader." + "\nField should be a valid python dict." + " Anything else will be ignored." + ) ), attribute_definitions.NumberDef( "order", @@ -331,9 +336,11 @@ Field should be a valid python dict. Anything else will be ignored. decimals=0, minimum=0, maximum=999, - tooltip="""Order -Order defines asset loading priority (0 to 999) -Priority rule is : "lowest is first to load".""" + tooltip=( + "Order" + "\nOrder defines asset loading priority (0 to 999)" + "\nPriority rule is : \"lowest is first to load\"." + ) ), attribute_definitions.UISeparatorDef(), attribute_definitions.UILabelDef("Optional attributes"), From f3ce64c1893bc2ea1b81fa254b6a8bc8734b587d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:22:19 +0200 Subject: [PATCH 264/513] copied placeholder name creation --- .../hosts/maya/api/new_template_builder.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index 06d1cf0fd7..36a74e9b9a 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -1,4 +1,6 @@ import re +import json + from maya import cmds from openpype.client import get_representations @@ -146,8 +148,27 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): return placeholder_data def _create_placeholder_name(self, placeholder_data): - # TODO implement placeholder name logic - return "Placeholder" + placeholder_name_parts = placeholder_data["builder_type"].split("_") + + pos = 1 + # add famlily in any + placeholder_family = placeholder_data["family"] + if placeholder_family: + placeholder_name_parts.insert(pos, placeholder_family) + pos += 1 + + # add loader arguments if any + loader_args = placeholder_data["loader_args"] + if loader_args: + loader_args = json.loads(loader_args.replace('\'', '\"')) + values = [v for v in loader_args.values()] + for value in values: + placeholder_name_parts.insert(pos, value) + pos += 1 + + placeholder_name = "_".join(placeholder_name_parts) + + return placeholder_name.capitalize() def create_placeholder(self, placeholder_data): selection = cmds.ls(selection=True) From 88f16988a4786c5b8144ca1d0988c27b973637e3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:22:57 +0200 Subject: [PATCH 265/513] fix build dialog and renamed it to placeholder dialog --- .../tools/workfile_template_build/__init__.py | 4 +- .../tools/workfile_template_build/window.py | 40 ++++++++++++------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/openpype/tools/workfile_template_build/__init__.py b/openpype/tools/workfile_template_build/__init__.py index 70b8867759..82a22aea50 100644 --- a/openpype/tools/workfile_template_build/__init__.py +++ b/openpype/tools/workfile_template_build/__init__.py @@ -1,5 +1,5 @@ -from .window import WorkfileBuildDialog +from .window import WorkfileBuildPlaceholderDialog __all__ = ( - "WorkfileBuildDialog", + "WorkfileBuildPlaceholderDialog", ) diff --git a/openpype/tools/workfile_template_build/window.py b/openpype/tools/workfile_template_build/window.py index a5cec465ec..2e531026cf 100644 --- a/openpype/tools/workfile_template_build/window.py +++ b/openpype/tools/workfile_template_build/window.py @@ -6,9 +6,9 @@ from openpype.pipeline import legacy_io from openpype.widgets.attribute_defs import AttributeDefinitionsWidget -class WorkfileBuildDialog(QtWidgets.QDialog): +class WorkfileBuildPlaceholderDialog(QtWidgets.QDialog): def __init__(self, host, builder, parent=None): - super(WorkfileBuildDialog, self).__init__(parent) + super(WorkfileBuildPlaceholderDialog, self).__init__(parent) self.setWindowTitle("Workfile Placeholder Manager") self._log = None @@ -78,7 +78,7 @@ class WorkfileBuildDialog(QtWidgets.QDialog): def _clear_content_widget(self): while self._content_layout.count() > 0: item = self._content_layout.takeAt(0) - widget = item.widget + widget = item.widget() if widget: widget.setVisible(False) widget.deleteLater() @@ -90,6 +90,7 @@ class WorkfileBuildDialog(QtWidgets.QDialog): def refresh(self): self._first_refreshed = True + self._clear_content_widget() if not self._builder: @@ -100,11 +101,19 @@ class WorkfileBuildDialog(QtWidgets.QDialog): self._update_ui_visibility() return + placeholder_plugins = self._builder.placeholder_plugins + if self._mode == 1: + self._last_selected_plugin + plugin = self._builder.placeholder_plugins.get( + self._last_selected_plugin + ) + self._create_option_widgets( + plugin, self._update_item.to_dict() + ) self._update_ui_visibility() return - placeholder_plugins = builder.placeholder_plugins if not placeholder_plugins: self._add_message_to_content(( "Host \"{}\" does not have implemented plugins" @@ -142,15 +151,16 @@ class WorkfileBuildDialog(QtWidgets.QDialog): self._mode = 1 self._update_item = update_item - if not update_item: - self._add_message_to_content(( - "Nothing to update." - " (You maybe don't have selected placeholder.)" - )) - else: - self._create_option_widgets( - update_item.plugin, update_item.to_dict() - ) + if update_item: + self._last_selected_plugin = update_item.plugin.identifier + self.refresh() + return + + self._clear_content_widget() + self._add_message_to_content(( + "Nothing to update." + " (You maybe don't have selected placeholder.)" + )) self._update_ui_visibility() def _create_option_widgets(self, plugin, options=None): @@ -160,6 +170,7 @@ class WorkfileBuildDialog(QtWidgets.QDialog): self._content_layout.addWidget(widget, 0) self._content_layout.addStretch(1) self._attr_defs_widget = widget + self._last_selected_plugin = plugin.identifier def _update_ui_visibility(self): create_mode = self._mode == 0 @@ -182,7 +193,6 @@ class WorkfileBuildDialog(QtWidgets.QDialog): if plugin_identifier == self._last_selected_plugin: return - self._last_selected_plugin = plugin_identifier plugin = self._builder.placeholder_plugins.get(plugin_identifier) self._create_option_widgets(plugin) @@ -222,7 +232,7 @@ class WorkfileBuildDialog(QtWidgets.QDialog): self.reject() def showEvent(self, event): - super(WorkfileBuildDialog, self).showEvent(event) + super(WorkfileBuildPlaceholderDialog, self).showEvent(event) if not self._first_refreshed: self.refresh() From 8f2bf758f3963badfa6a211211eda4ea5efe9326 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:23:31 +0200 Subject: [PATCH 266/513] fixed get of value from attribute defs widget --- openpype/widgets/attribute_defs/widgets.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py index 60ae952553..dc697b08a6 100644 --- a/openpype/widgets/attribute_defs/widgets.py +++ b/openpype/widgets/attribute_defs/widgets.py @@ -108,10 +108,12 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget): row = 0 for attr_def in attr_defs: - if attr_def.key in self._current_keys: - raise KeyError("Duplicated key \"{}\"".format(attr_def.key)) + if not isinstance(attr_def, UIDef): + if attr_def.key in self._current_keys: + raise KeyError( + "Duplicated key \"{}\"".format(attr_def.key)) - self._current_keys.add(attr_def.key) + self._current_keys.add(attr_def.key) widget = create_widget_for_attr_def(attr_def, self) expand_cols = 2 From 187eaaec2d9f17d01cff4cc43bd3b01c58d679e4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:24:37 +0200 Subject: [PATCH 267/513] renamed 'process_scene_placeholders' to 'populate_scene_placeholders' --- .../pipeline/workfile/new_template_loader.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index b1231c2308..a59394b09c 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -233,7 +233,7 @@ class AbstractTemplateLoader: Import template in current host. Should load the content of template into scene so - 'process_scene_placeholders' can be started. + 'populate_scene_placeholders' can be started. Args: template_path (str): Fullpath for current task and @@ -270,7 +270,7 @@ class AbstractTemplateLoader: plugin = plugins_by_identifier[identifier] plugin.prepare_placeholders(placeholders) - def process_scene_placeholders(self, level_limit=None): + def populate_scene_placeholders(self, level_limit=None): """Find placeholders in scene using plugins and process them. This should happen after 'import_template'. @@ -285,8 +285,7 @@ class AbstractTemplateLoader: placeholder's 'scene_identifier'. Args: - level_limit (int): Level of loops that can happen. By default - if is possible to have infinite nested placeholder processing. + level_limit (int): Level of loops that can happen. Default is 1000. """ if not self.placeholder_plugins: @@ -298,6 +297,11 @@ class AbstractTemplateLoader: self.log.warning("No placeholders were found.") return + # Avoid infinite loop + # - 1000 iterations of placeholders processing must be enough + if not level_limit: + level_limit = 1000 + placeholder_by_scene_id = { placeholder.scene_identifier: placeholder for placeholder in placeholders @@ -335,10 +339,9 @@ class AbstractTemplateLoader: # Clear shared data before getting new placeholders self.clear_shared_data() - if level_limit: - iter_counter += 1 - if iter_counter >= level_limit: - break + iter_counter += 1 + if iter_counter >= level_limit: + break all_processed = True collected_placeholders = self.get_placeholders() From 1ee1c2858856eae18b38244ea179580bb5ba2fb9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:24:54 +0200 Subject: [PATCH 268/513] added method to build template --- openpype/pipeline/workfile/new_template_loader.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index a59394b09c..3fee84a4e8 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -227,6 +227,12 @@ class AbstractTemplateLoader: key=lambda i: i.order )) + def build_template(self, template_path=None, level_limit=None): + if template_path is None: + template_path = self.get_template_path() + self.import_template(template_path) + self.populate_scene_placeholders(level_limit) + @abstractmethod def import_template(self, template_path): """ From 326d21d86a2d99f36bc8f420b20b271bd0a894b1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:25:11 +0200 Subject: [PATCH 269/513] use 'get_placeholder_plugin_classes' from host --- openpype/pipeline/workfile/new_template_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 3fee84a4e8..dbf58fe15d 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -174,7 +174,7 @@ class AbstractTemplateLoader: if self._placeholder_plugins is None: placeholder_plugins = {} - for cls in self.get_placeholder_plugin_classes(): + for cls in self.host.get_placeholder_plugin_classes(): try: plugin = cls(self) placeholder_plugins[plugin.identifier] = plugin From ffab250bf822a6440670ece4d815199f4826a52a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:26:45 +0200 Subject: [PATCH 270/513] changed how 'get_placeholder_plugin_classes' is implemented --- .../pipeline/workfile/new_template_loader.py | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index dbf58fe15d..64e3021f00 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -8,7 +8,12 @@ import six from openpype.client import get_asset_by_name from openpype.settings import get_project_settings from openpype.host import HostBase -from openpype.lib import Logger, StringTemplate, filter_profiles +from openpype.lib import ( + Logger, + StringTemplate, + filter_profiles, +) +from openpype.lib.attribute_definitions import get_attributes_keys from openpype.pipeline import legacy_io, Anatomy from openpype.pipeline.load import get_loaders_by_name from openpype.pipeline.create import get_legacy_creator_by_name @@ -31,12 +36,26 @@ class AbstractTemplateLoader: _log = None def __init__(self, host): - # Store host - self._host = host + # Prepare context information + project_name = legacy_io.active_project() + asset_name = legacy_io.Session["AVALON_ASSET"] + task_name = legacy_io.Session["AVALON_TASK"] + current_asset_doc = get_asset_by_name(project_name, asset_name) + task_type = ( + current_asset_doc + .get("data", {}) + .get("tasks", {}) + .get(task_name, {}) + .get("type") + ) + + # Get host name if isinstance(host, HostBase): host_name = host.name else: host_name = os.environ.get("AVALON_APP") + + self._host = host self._host_name = host_name # Shared data across placeholder plugins @@ -47,29 +66,24 @@ class AbstractTemplateLoader: self._loaders_by_name = None self._creators_by_name = None - project_name = legacy_io.active_project() - asset_name = legacy_io.Session["AVALON_ASSET"] - self.current_asset = asset_name self.project_name = project_name - self.task_name = legacy_io.Session["AVALON_TASK"] - self.current_asset_doc = get_asset_by_name(project_name, asset_name) - self.task_type = ( - self.current_asset_doc - .get("data", {}) - .get("tasks", {}) - .get(self.task_name, {}) - .get("type") - ) + self.task_name = task_name + self.current_asset_doc = current_asset_doc + self.task_type = task_type - @abstractmethod def get_placeholder_plugin_classes(self): """Get placeholder plugin classes that can be used to build template. + Default implementation looks for 'get_placeholder_plugin_classes' on + host. + Returns: List[PlaceholderPlugin]: Plugin classes available for host. """ + if hasattr(self._host, "get_placeholder_plugin_classes"): + return self._host.get_placeholder_plugin_classes() return [] @property @@ -174,7 +188,7 @@ class AbstractTemplateLoader: if self._placeholder_plugins is None: placeholder_plugins = {} - for cls in self.host.get_placeholder_plugin_classes(): + for cls in self.get_placeholder_plugin_classes(): try: plugin = cls(self) placeholder_plugins[plugin.identifier] = plugin From d5346a14d90f7746d7ee589e17d6315c0f7baaea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 12:28:32 +0200 Subject: [PATCH 271/513] implemented helper functions to receive placeholder option keys --- openpype/lib/attribute_definitions.py | 21 +++++++++++++++++++ .../pipeline/workfile/new_template_loader.py | 11 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 17658eef93..cbd53d1f07 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -9,6 +9,27 @@ import six import clique +def get_attributes_keys(attribute_definitions): + """Collect keys from list of attribute definitions. + + Args: + attribute_definitions (List[AbtractAttrDef]): Objects of attribute + definitions. + + Returns: + Set[str]: Keys that will be created using passed attribute definitions. + """ + + keys = set() + if not attribute_definitions: + return keys + + for attribute_def in attribute_definitions: + if not isinstance(attribute_def, UIDef): + keys.add(attribute_def.key) + return keys + + class AbstractAttrDefMeta(ABCMeta): """Meta class to validate existence of 'key' attribute. diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 64e3021f00..21bfa3650d 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -547,6 +547,17 @@ class PlaceholderPlugin(object): return [] + def get_placeholder_keys(self): + """Get placeholder keys that are stored in scene. + + Returns: + Set[str]: Key of placeholder keys that are stored in scene. + """ + + option_keys = get_attributes_keys(self.get_placeholder_options()) + option_keys.add("plugin_identifier") + return option_keys + def prepare_placeholders(self, placeholders): """Preparation part of placeholders. From d0665c3928ecff4176f6e22b676384f09485c173 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 13:06:12 +0200 Subject: [PATCH 272/513] Change `mayaascii` -> `mayaAscii` --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 2 +- openpype/settings/defaults/project_settings/ftrack.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 6024781d87..7e5815b100 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -35,7 +35,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): family_mapping = { "camera": "cam", "look": "look", - "mayaascii": "scene", + "mayaAscii": "scene", "model": "geo", "rig": "rig", "setdress": "setdress", diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 09b194e21c..cdf861df4a 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -455,7 +455,7 @@ "family_mapping": { "camera": "cam", "look": "look", - "mayaascii": "scene", + "mayaAscii": "scene", "model": "geo", "rig": "rig", "setdress": "setdress", From 6401c4064150f316ae96cd39585edf6b77a4cdac Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 13:14:05 +0200 Subject: [PATCH 273/513] changed how placeholders are cached --- .../hosts/maya/api/new_template_builder.py | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index 36a74e9b9a..b25d75452c 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -44,7 +44,7 @@ class MayaTemplateLoader(AbstractTemplateLoader): cmds.sets(name=PLACEHOLDER_SET, empty=True) cmds.file(path, i=True, returnNewNodes=True) - cmds.setAttr(PLACEHOLDER_SET + '.hiddenInOutliner', True) + cmds.setAttr(PLACEHOLDER_SET + ".hiddenInOutliner", True) # This should be handled by creators # for set_name in cmds.listSets(allSets=True): @@ -116,10 +116,13 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): placeholder_nodes = self.builder.get_shared_data("placeholder_nodes") if placeholder_nodes is None: attributes = cmds.ls("*.plugin_identifier", long=True) - placeholder_nodes = [ - self._parse_placeholder_node_data(attribute.rpartition(".")[0]) - for attribute in attributes - ] + placeholder_nodes = {} + for attribute in attributes: + node_name = attribute.rpartition(".")[0] + placeholder_nodes[node_name] = ( + self._parse_placeholder_node_data(node_name) + ) + self.builder.set_shared_data( "placeholder_nodes", placeholder_nodes ) @@ -182,7 +185,6 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): placeholder_name = self._create_placeholder_name(placeholder_data) placeholder = cmds.spaceLocator(name=placeholder_name)[0] - # TODO: this can crash if selection can't be used cmds.parent(placeholder, selection[0]) @@ -221,20 +223,23 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): new_values[key] = value placeholder_item.data[key] = value + for key in new_values.keys(): + cmds.deleteAttr(node_name + "." + key) + imprint(node_name, new_values) def collect_placeholders(self): - filtered_placeholders = [] - for placeholder_data in self._collect_scene_placeholders(): + output = [] + scene_placeholders = self._collect_scene_placeholders() + for node_name, placeholder_data in scene_placeholders.items(): if placeholder_data.get("plugin_identifier") != self.identifier: continue - filtered_placeholders.append(placeholder_data) - - output = [] - for placeholder_data in filtered_placeholders: # TODO do data validations and maybe updgrades if are invalid - output.append(LoadPlaceholder(placeholder_data)) + output.append( + LoadPlaceholder(node_name, placeholder_data, self) + ) + return output def process_placeholder(self, placeholder): @@ -429,8 +434,8 @@ class LoadPlaceholder(PlaceholderItem): elif not cmds.sets(root, q=True): return - if self.data['parent']: - cmds.parent(nodes_to_parent, self.data['parent']) + if self.data["parent"]: + cmds.parent(nodes_to_parent, self.data["parent"]) # Move loaded nodes to correct index in outliner hierarchy placeholder_form = cmds.xform( self._scene_identifier, @@ -440,7 +445,7 @@ class LoadPlaceholder(PlaceholderItem): ) for node in set(nodes_to_parent): cmds.reorder(node, front=True) - cmds.reorder(node, relative=self.data['index']) + cmds.reorder(node, relative=self.data["index"]) cmds.xform(node, matrix=placeholder_form, ws=True) holding_sets = cmds.listSets(object=self._scene_identifier) @@ -470,7 +475,7 @@ class LoadPlaceholder(PlaceholderItem): node = cmds.parent(node, world=True)[0] cmds.sets(node, addElement=PLACEHOLDER_SET) cmds.hide(node) - cmds.setAttr(node + '.hiddenInOutliner', True) + cmds.setAttr(node + ".hiddenInOutliner", True) def get_representations(self, current_asset_doc, linked_asset_docs): project_name = legacy_io.active_project() From 5ccda391e4e4c980915c8f6954b5b43aaf454d86 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 13:23:10 +0200 Subject: [PATCH 274/513] changed loaders --- openpype/hosts/maya/api/new_template_builder.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index b25d75452c..bc1a0250a8 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -286,7 +286,12 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): def get_placeholder_options(self, options=None): loaders_by_name = self.builder.get_loaders_by_name() - loader_names = list(sorted(loaders_by_name.keys())) + loader_items = [ + (loader_name, loader.label or loader_name) + for loader_name, loader in loaders_by_name.items() + ] + + loader_items = list(sorted(loader_items, key=lambda i: i[0])) options = options or {} return [ attribute_definitions.UISeparatorDef(), @@ -330,10 +335,7 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): "loader", label="Loader", default=options.get("loader"), - items=[ - (loader_name, loader_name) - for loader_name in loader_names - ], + items=loader_items, tooltip=( "Loader" "\nDefines what OpenPype loader will be used to" From e5654595a83d88a5ac4dcea8bd7c9b7fe360f3bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 13:31:55 +0200 Subject: [PATCH 275/513] implemented 'get_workfile_build_placeholder_plugins' for maya --- openpype/hosts/maya/api/pipeline.py | 6 ++++++ openpype/pipeline/workfile/new_template_loader.py | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index c963b5d996..2492e75b36 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -35,6 +35,7 @@ from openpype.hosts.maya import MAYA_ROOT_DIR from openpype.hosts.maya.lib import copy_workspace_mel from . import menu, lib +from .new_template_builder import MayaLoadPlaceholderPlugin from .workio import ( open_file, save_file, @@ -123,6 +124,11 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): def get_containers(self): return ls() + def get_workfile_build_placeholder_plugins(self): + return [ + MayaLoadPlaceholderPlugin + ] + @contextlib.contextmanager def maintained_selection(self): with lib.maintained_selection(): diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 21bfa3650d..8b64306c18 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -75,15 +75,15 @@ class AbstractTemplateLoader: def get_placeholder_plugin_classes(self): """Get placeholder plugin classes that can be used to build template. - Default implementation looks for 'get_placeholder_plugin_classes' on - host. + Default implementation looks for method + 'get_workfile_build_placeholder_plugins' on host. Returns: List[PlaceholderPlugin]: Plugin classes available for host. """ - if hasattr(self._host, "get_placeholder_plugin_classes"): - return self._host.get_placeholder_plugin_classes() + if hasattr(self._host, "get_workfile_build_placeholder_plugins"): + return self._host.get_workfile_build_placeholder_plugins() return [] @property From c622eb7a59986ebe003205d66cd2ae101e1b23eb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Sep 2022 13:54:33 +0200 Subject: [PATCH 276/513] flame: add ui to project media panel --- openpype/hosts/flame/api/__init__.py | 4 ++-- openpype/hosts/flame/api/menu.py | 2 +- openpype/hosts/flame/startup/openpype_in_flame.py | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index 7da91d41e4..c00ee958b6 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -52,7 +52,7 @@ from .pipeline import ( from .menu import ( FlameMenuProjectConnect, FlameMenuTimeline, - FlameMenuBatch + FlameMenuUniversal ) from .plugin import ( Creator, @@ -132,7 +132,7 @@ __all__ = [ # menu "FlameMenuProjectConnect", "FlameMenuTimeline", - "FlameMenuBatch", + "FlameMenuUniversal", # plugin "Creator", diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index a822059930..f72a352bba 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -203,7 +203,7 @@ class FlameMenuTimeline(_FlameMenuApp): self.log.info('Rescan Python Hooks') -class FlameMenuBatch(_FlameMenuApp): +class FlameMenuUniversal(_FlameMenuApp): # flameMenuProjectconnect app takes care of the preferences dialog as well diff --git a/openpype/hosts/flame/startup/openpype_in_flame.py b/openpype/hosts/flame/startup/openpype_in_flame.py index 60f6612b7f..9fdc30db5d 100644 --- a/openpype/hosts/flame/startup/openpype_in_flame.py +++ b/openpype/hosts/flame/startup/openpype_in_flame.py @@ -194,6 +194,7 @@ def get_timeline_custom_ui_actions(): return _build_app_menu("FlameMenuTimeline") + def get_batch_custom_ui_actions(): """Hook to create submenu in batch @@ -203,4 +204,16 @@ def get_batch_custom_ui_actions(): # install openpype and the host openpype_install() - return _build_app_menu("FlameMenuBatch") \ No newline at end of file + return _build_app_menu("FlameMenuUniversal") + + +def get_media_panel_custom_ui_actions(): + """Hook to create submenu in desktop + + Returns: + list: menu object + """ + # install openpype and the host + openpype_install() + + return _build_app_menu("FlameMenuUniversal") From 7b78e09eaec1c7e243782c13079ff70ca6c06d23 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Sep 2022 13:59:15 +0200 Subject: [PATCH 277/513] fixing name of class --- openpype/hosts/flame/startup/openpype_in_flame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/startup/openpype_in_flame.py b/openpype/hosts/flame/startup/openpype_in_flame.py index 9fdc30db5d..d07aaa6b7d 100644 --- a/openpype/hosts/flame/startup/openpype_in_flame.py +++ b/openpype/hosts/flame/startup/openpype_in_flame.py @@ -74,7 +74,7 @@ def load_apps(): opfapi.CTX.flame_apps.append( opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) opfapi.CTX.flame_apps.append( - opfapi.FlameMenuBatch(opfapi.CTX.app_framework)) + opfapi.FlameMenuUniversal(opfapi.CTX.app_framework)) opfapi.CTX.app_framework.log.info("Apps are loaded") From 42f575fca94722a25ae462a5112a843799d9aad1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 14:23:36 +0200 Subject: [PATCH 278/513] actions are expected as list so each application in group is stored if force not open workfile is enabled --- openpype/tools/launcher/models.py | 46 +++++++++++++++++++------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 6d40d21f96..6e3b531018 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -281,18 +281,25 @@ class ActionModel(QtGui.QStandardItemModel): if not action_item: return - action = action_item.data(ACTION_ROLE) - actual_data = self._prepare_compare_data(action) + actions = action_item.data(ACTION_ROLE) + if not isinstance(actions, list): + actions = [actions] + + action_actions_data = [ + self._prepare_compare_data(action) + for action in actions + ] stored = self.launcher_registry.get_item("force_not_open_workfile") - if is_checked: - stored.append(actual_data) - else: - final_values = [] - for config in stored: - if config != actual_data: - final_values.append(config) - stored = final_values + for actual_data in action_actions_data: + if is_checked: + stored.append(actual_data) + else: + final_values = [] + for config in stored: + if config != actual_data: + final_values.append(config) + stored = final_values self.launcher_registry.set_item("force_not_open_workfile", stored) self.launcher_registry._get_item.cache_clear() @@ -329,21 +336,24 @@ class ActionModel(QtGui.QStandardItemModel): item (QStandardItem) stored (list) of dict """ - action = item.data(ACTION_ROLE) - if not self.is_application_action(action): + + actions = item.data(ACTION_ROLE) + if not isinstance(actions, list): + actions = [actions] + + if not self.is_application_action(actions[0]): return False - actual_data = self._prepare_compare_data(action) + action_actions_data = [ + self._prepare_compare_data(action) + for action in actions + ] for config in stored: - if config == actual_data: + if config in action_actions_data: return True - return False def _prepare_compare_data(self, action): - if isinstance(action, list) and action: - action = action[0] - compare_data = {} if action and action.label: compare_data = { From 98c065cb8b3c8f75ae0479da1a3287ebbe0b22d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 14:24:02 +0200 Subject: [PATCH 279/513] change "start_last_workfile" when triggered from group --- openpype/tools/launcher/widgets.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 62599664fe..774ceb659d 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -312,11 +312,12 @@ class ActionBar(QtWidgets.QWidget): is_group = index.data(GROUP_ROLE) is_variant_group = index.data(VARIANT_GROUP_ROLE) + force_not_open_workfile = index.data(FORCE_NOT_OPEN_WORKFILE_ROLE) if not is_group and not is_variant_group: action = index.data(ACTION_ROLE) # Change data of application action if issubclass(action, ApplicationAction): - if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE): + if force_not_open_workfile: action.data["start_last_workfile"] = False else: action.data.pop("start_last_workfile", None) @@ -385,10 +386,18 @@ class ActionBar(QtWidgets.QWidget): menu.addMenu(sub_menu) result = menu.exec_(QtGui.QCursor.pos()) - if result: - action = actions_mapping[result] - self._start_animation(index) - self.action_clicked.emit(action) + if not result: + return + + action = actions_mapping[result] + if issubclass(action, ApplicationAction): + if force_not_open_workfile: + action.data["start_last_workfile"] = False + else: + action.data.pop("start_last_workfile", None) + + self._start_animation(index) + self.action_clicked.emit(action) class ActionHistory(QtWidgets.QPushButton): From d3eca627de790a3e73c58cf96ff03f14bf2f7d96 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 16:52:39 +0200 Subject: [PATCH 280/513] added some more options for shared data --- .../pipeline/workfile/new_template_loader.py | 122 ++++++++++++++++-- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 8b64306c18..baca11d730 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -60,6 +60,7 @@ class AbstractTemplateLoader: # Shared data across placeholder plugins self._shared_data = {} + self._shared_populate_data = {} # Where created objects of placeholder plugins will be stored self._placeholder_plugins = None @@ -121,14 +122,7 @@ class AbstractTemplateLoader: self._loaders_by_name = None self._creators_by_name = None self.clear_shared_data() - - def clear_shared_data(self): - """Clear shared data. - - Method only clear shared data to default state. - """ - - self._shared_data = {} + self.clear_shared_populate_data() def get_loaders_by_name(self): if self._loaders_by_name is None: @@ -147,8 +141,6 @@ class AbstractTemplateLoader: items if the storing is unified but each placeholder plugin would have to call it again. - Shared data are cleaned up on specific callbacks. - Args: key (str): Key under which are shared data stored. @@ -169,8 +161,6 @@ class AbstractTemplateLoader: - wrong: 'asset' - good: 'asset_name' - Shared data are cleaned up on specific callbacks. - Args: key (str): Key under which is key stored. value (Any): Value that should be stored under the key. @@ -178,6 +168,72 @@ class AbstractTemplateLoader: self._shared_data[key] = value + def clear_shared_data(self): + """Clear shared data. + + Method only clear shared data to default state. + """ + + self._shared_data = {} + + def clear_shared_populate_data(self): + """Receive shared data across plugins and placeholders. + + These data are cleared after each loop of populating of template. + + This can be used to scroll scene only once to look for placeholder + items if the storing is unified but each placeholder plugin would have + to call it again. + + Args: + key (str): Key under which are shared data stored. + + Returns: + Union[None, Any]: None if key was not set. + """ + + self._shared_populate_data = {} + + def get_shared_populate_data(self, key): + """Store share populate data across plugins and placeholders. + + These data are cleared after each loop of populating of template. + + Store data that can be afterwards accessed from any future call. It + is good practice to check if the same value is not already stored under + different key or if the key is not already used for something else. + + Key should be self explanatory to content. + - wrong: 'asset' + - good: 'asset_name' + + Args: + key (str): Key under which is key stored. + value (Any): Value that should be stored under the key. + """ + + return self._shared_populate_data.get(key) + + def set_shared_populate_data(self, key, value): + """Store share populate data across plugins and placeholders. + + These data are cleared after each loop of populating of template. + + Store data that can be afterwards accessed from any future call. It + is good practice to check if the same value is not already stored under + different key or if the key is not already used for something else. + + Key should be self explanatory to content. + - wrong: 'asset' + - good: 'asset_name' + + Args: + key (str): Key under which is key stored. + value (Any): Value that should be stored under the key. + """ + + self._shared_populate_data[key] = value + @property def placeholder_plugins(self): """Access to initialized placeholder plugins. @@ -636,6 +692,48 @@ class PlaceholderPlugin(object): plugin_data[key] = value self.builder.set_shared_data(self.identifier, plugin_data) + def get_plugin_shared_populate_data(self, key): + """Receive shared data across plugin and placeholders. + + Using shared populate data from builder but stored under plugin + identifier. + + Shared populate data are cleaned up during populate while loop. + + Args: + key (str): Key under which are shared data stored. + + Returns: + Union[None, Any]: None if key was not set. + """ + + plugin_data = self.builder.get_shared_populate_data(self.identifier) + if plugin_data is None: + return None + return plugin_data.get(key) + + def set_plugin_shared_populate_data(self, key, value): + """Store share data across plugin and placeholders. + + Using shared data from builder but stored under plugin identifier. + + Key should be self explanatory to content. + - wrong: 'asset' + - good: 'asset_name' + + Shared populate data are cleaned up during populate while loop. + + Args: + key (str): Key under which is key stored. + value (Any): Value that should be stored under the key. + """ + + plugin_data = self.builder.get_shared_populate_data(self.identifier) + if plugin_data is None: + plugin_data = {} + plugin_data[key] = value + self.builder.set_shared_populate_data(self.identifier, plugin_data) + class PlaceholderItem(object): """Item representing single item in scene that is a placeholder to process. From e45fd5f99507d4cf6c8e4b7220651741b3bf564e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 16:53:06 +0200 Subject: [PATCH 281/513] changed 'process_placeholder' to 'populate_placeholder' --- openpype/pipeline/workfile/new_template_loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index baca11d730..953b7771b2 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -404,7 +404,7 @@ class AbstractTemplateLoader: placeholder.set_in_progress() placeholder_plugin = placeholder.plugin try: - placeholder_plugin.process_placeholder(placeholder) + placeholder_plugin.populate_placeholder(placeholder) except Exception as exc: placeholder.set_error(exc) @@ -625,7 +625,7 @@ class PlaceholderPlugin(object): pass @abstractmethod - def process_placeholder(self, placeholder): + def populate_placeholder(self, placeholder): """Process single placeholder item. Processing of placeholders is defined by their order thus can't be From 77d8d1b1ce05872a613e3e54ff1d2e182d4e5eb4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 16:53:21 +0200 Subject: [PATCH 282/513] implemented basics of update template placeholder --- .../pipeline/workfile/new_template_loader.py | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 953b7771b2..b97e48dcba 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -303,6 +303,37 @@ class AbstractTemplateLoader: self.import_template(template_path) self.populate_scene_placeholders(level_limit) + def update_template(self): + """Go through existing placeholders in scene and update them. + + This could not make sense for all plugin types so this is optional + logic for plugins. + + Note: + Logic is not importing the template again but using placeholders + that were already available. We should maybe change the method + name. + + Question: + Should this also handle subloops as it is possible that another + template is loaded during processing? + """ + + if not self.placeholder_plugins: + self.log.info("There are no placeholder plugins available.") + return + + placeholders = self.get_placeholders() + if not placeholders: + self.log.info("No placeholders were found.") + return + + for placeholder in placeholders: + plugin = placeholder.plugin + plugin.update_template_placeholder(placeholder) + + self.clear_shared_populate_data() + @abstractmethod def import_template(self, template_path): """ @@ -318,12 +349,6 @@ class AbstractTemplateLoader: pass - # def template_already_imported(self, err_msg): - # pass - # - # def template_loading_failed(self, err_msg): - # pass - def _prepare_placeholders(self, placeholders): """Run preparation part for placeholders on plugins. @@ -413,7 +438,7 @@ class AbstractTemplateLoader: placeholder.set_finished() # Clear shared data before getting new placeholders - self.clear_shared_data() + self.clear_shared_populate_data() iter_counter += 1 if iter_counter >= level_limit: @@ -430,6 +455,8 @@ class AbstractTemplateLoader: placeholder_by_scene_id[identifier] = placeholder placeholders.append(placeholder) + self.refresh() + def _get_build_profiles(self): project_settings = get_project_settings(self.project_name) return ( @@ -582,6 +609,7 @@ class PlaceholderPlugin(object): placeholder_data (Dict[str, Any]): Data related to placeholder. Should match plugin options. """ + pass @abstractmethod @@ -638,15 +666,10 @@ class PlaceholderPlugin(object): pass - def cleanup_placeholders(self, placeholders): - """Cleanup of placeholders after processing. + def update_template_placeholder(self, placeholder): + """Update scene with current context for passed placeholder. - Not: - Passed placeholders can be failed. - - Args: - placeholders (List[PlaceholderItem]): List of placeholders that - were be processed. + Can be used to re-run placeholder logic (if it make sense). """ pass @@ -656,8 +679,6 @@ class PlaceholderPlugin(object): Using shared data from builder but stored under plugin identifier. - Shared data are cleaned up on specific callbacks. - Args: key (str): Key under which are shared data stored. @@ -679,8 +700,6 @@ class PlaceholderPlugin(object): - wrong: 'asset' - good: 'asset_name' - Shared data are cleaned up on specific callbacks. - Args: key (str): Key under which is key stored. value (Any): Value that should be stored under the key. From 9821a05cdcf201b14319ba4a054adc735ea8d69a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 16:54:58 +0200 Subject: [PATCH 283/513] implemented update logic for maya load plugin --- .../hosts/maya/api/new_template_builder.py | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index bc1a0250a8..9017e447c5 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -173,6 +173,25 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): return placeholder_name.capitalize() + def _get_loaded_repre_ids(self): + loaded_representation_ids = self.builder.get_shared_populate_data( + "loaded_representation_ids" + ) + if loaded_representation_ids is None: + try: + containers = cmds.sets("AVALON_CONTAINERS", q=True) + except ValueError: + containers = [] + + loaded_representation_ids = { + cmds.getAttr(container + ".representation") + for container in containers + } + self.builder.set_shared_populate_data( + "loaded_representation_ids" + ) + return loaded_representation_ids + def create_placeholder(self, placeholder_data): selection = cmds.ls(selection=True) if not selection: @@ -242,7 +261,17 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): return output - def process_placeholder(self, placeholder): + def populate_placeholder(self, placeholder): + self._populate_placeholder(placeholder) + + def update_template_placeholder(self, placeholder): + repre_ids = self._get_loaded_repre_ids() + self._populate_placeholder(placeholder, repre_ids) + + def _populate_placeholder(self, placeholder, ignore_repre_ids=None): + if ignore_repre_ids is None: + ignore_repre_ids = set() + current_asset_doc = self.current_asset_doc linked_assets = self.linked_assets loader_name = placeholder.data["loader"] @@ -262,6 +291,10 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): loaders_by_name = self.builder.get_loaders_by_name() for representation in placeholder_representations: + repre_id = str(representation["_id"]) + if repre_id in ignore_repre_ids: + continue + repre_context = representation["context"] self.log.info( "Loading {} from {} with loader {}\n" @@ -280,9 +313,7 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): else: placeholder.load_succeed(container) - # TODO find out if 'postload make sense?' - # finally: - # self.postload(placeholder) + placeholder.clean() def get_placeholder_options(self, options=None): loaders_by_name = self.builder.get_loaders_by_name() From f133f401c059a4b03033e995ea67460de6fac732 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 16:55:09 +0200 Subject: [PATCH 284/513] small tweaks in code --- openpype/hosts/maya/api/new_template_builder.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index 9017e447c5..b28cc80cd1 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -61,11 +61,6 @@ class MayaTemplateLoader(AbstractTemplateLoader): return True - def get_placeholder_plugin_classes(self): - return [ - MayaLoadPlaceholderPlugin - ] - # def template_already_imported(self, err_msg): # clearButton = "Clear scene and build" # updateButton = "Update template" @@ -113,7 +108,9 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): def _collect_scene_placeholders(self): # Cache placeholder data to shared data - placeholder_nodes = self.builder.get_shared_data("placeholder_nodes") + placeholder_nodes = self.builder.get_shared_populate_data( + "placeholder_nodes" + ) if placeholder_nodes is None: attributes = cmds.ls("*.plugin_identifier", long=True) placeholder_nodes = {} @@ -123,7 +120,7 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): self._parse_placeholder_node_data(node_name) ) - self.builder.set_shared_data( + self.builder.set_shared_populate_data( "placeholder_nodes", placeholder_nodes ) return placeholder_nodes From 8d60739fe43b96ea227e2af6b3d7889504448ae6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 16:55:24 +0200 Subject: [PATCH 285/513] implemented functions for building actions --- .../hosts/maya/api/new_template_builder.py | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index b28cc80cd1..43e92c59e2 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -5,7 +5,7 @@ from maya import cmds from openpype.client import get_representations from openpype.lib import attribute_definitions -from openpype.pipeline import legacy_io +from openpype.pipeline import legacy_io, registered_host from openpype.pipeline.workfile.build_template_exceptions import ( TemplateAlreadyImported ) @@ -14,6 +14,9 @@ from openpype.pipeline.workfile.new_template_loader import ( PlaceholderPlugin, PlaceholderItem, ) +from openpype.tools.workfile_template_build import ( + WorkfileBuildPlaceholderDialog, +) from .lib import read, imprint @@ -566,3 +569,45 @@ class LoadPlaceholder(PlaceholderItem): def load_succeed(self, container): self.parent_in_hierarchy(container) + + +def build_workfile_template(): + builder = MayaTemplateLoader(registered_host()) + builder.build_template() + + +def update_workfile_template(): + builder = MayaTemplateLoader(registered_host()) + builder.update_build_template() + + +def create_placeholder(): + host = registered_host() + builder = MayaTemplateLoader(host) + window = WorkfileBuildPlaceholderDialog(host, builder) + window.exec_() + + +def update_placeholder(): + host = registered_host() + builder = MayaTemplateLoader(host) + placeholder_items_by_id = { + placeholder_item.scene_identifier: placeholder_item + for placeholder_item in builder.get_placeholders() + } + placeholder_items = [] + for node_name in cmds.ls(selection=True, long=True): + if node_name in placeholder_items_by_id: + placeholder_items.append(placeholder_items_by_id[node_name]) + + # TODO show UI at least + if len(placeholder_items) == 0: + raise ValueError("No node selected") + + if len(placeholder_items) > 1: + raise ValueError("Too many selected nodes") + + placeholder_item = placeholder_items[0] + window = WorkfileBuildPlaceholderDialog(host, builder) + window.set_update_mode(placeholder_item) + window.exec_() From 0102614531c70902c37b5bde494daf65718c805b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 16:55:34 +0200 Subject: [PATCH 286/513] use functions in menu --- openpype/hosts/maya/api/menu.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index 666f555660..0b3ea81403 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -9,16 +9,17 @@ import maya.cmds as cmds from openpype.settings import get_project_settings from openpype.pipeline import legacy_io from openpype.pipeline.workfile import BuildWorkfile -from openpype.pipeline.workfile.build_template import ( - build_workfile_template, - update_workfile_template -) from openpype.tools.utils import host_tools from openpype.hosts.maya.api import lib, lib_rendersettings from .lib import get_main_window, IS_HEADLESS from .commands import reset_frame_range -from .lib_template_builder import create_placeholder, update_placeholder +from .new_template_builder import ( + create_placeholder, + update_placeholder, + build_workfile_template, + update_workfile_template, +) log = logging.getLogger(__name__) From 113210a781ed5affd6e92f72d075a9522a07c0f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 17:21:53 +0200 Subject: [PATCH 287/513] firx access to 'Pattern' attribute --- openpype/client/entities.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index a9112ac581..43afccf2f1 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -14,6 +14,8 @@ from bson.objectid import ObjectId from .mongo import get_project_database, get_project_connection +PatternType = type(re.compile("")) + def _prepare_fields(fields, required_fields=None): if not fields: @@ -1054,11 +1056,11 @@ def _regex_filters(filters): for key, value in filters.items(): regexes = [] a_values = [] - if isinstance(value, re.Pattern): + if isinstance(value, PatternType): regexes.append(value) elif isinstance(value, (list, tuple, set)): for item in value: - if isinstance(item, re.Pattern): + if isinstance(item, PatternType): regexes.append(item) else: a_values.append(item) @@ -1194,7 +1196,7 @@ def get_representations( as filter. Filter ignored if 'None' is passed. version_ids (Iterable[str]): Subset ids used as parent filter. Filter ignored if 'None' is passed. - context_filters (Dict[str, List[str, re.Pattern]]): Filter by + context_filters (Dict[str, List[str, PatternType]]): Filter by representation context fields. names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering using version ids and list of names under the version. @@ -1240,7 +1242,7 @@ def get_archived_representations( as filter. Filter ignored if 'None' is passed. version_ids (Iterable[str]): Subset ids used as parent filter. Filter ignored if 'None' is passed. - context_filters (Dict[str, List[str, re.Pattern]]): Filter by + context_filters (Dict[str, List[str, PatternType]]): Filter by representation context fields. names_by_version_ids (dict[ObjectId, List[str]]): Complex filtering using version ids and list of names under the version. From c5ae7e5d6cb704794e444addf93bb9e903795dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 9 Sep 2022 17:45:44 +0200 Subject: [PATCH 288/513] Update openpype/hosts/flame/plugins/publish/extract_subset_resources.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 1af6b00654..1d42330e23 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -96,7 +96,7 @@ class ExtractSubsetResources(openpype.api.Extractor): source_start_handles = instance.data["sourceStartH"] source_end_handles = instance.data["sourceEndH"] - # retime if needed + # retime if needed if r_speed != 1.0: if retimed_handles: # handles are retimed From b25270ccddbb7b47cbc6bca978a8c539096e7798 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 17:58:07 +0200 Subject: [PATCH 289/513] use direct function calls --- openpype/hosts/maya/api/menu.py | 4 ++-- openpype/hosts/maya/api/new_template_builder.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index 0b3ea81403..cc9a17fd72 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -162,12 +162,12 @@ def install(): cmds.menuItem( "Create Placeholder", parent=builder_menu, - command=lambda *args: create_placeholder() + command=create_placeholder ) cmds.menuItem( "Update Placeholder", parent=builder_menu, - command=lambda *args: update_placeholder() + command=update_placeholder ) cmds.menuItem( "Build Workfile from template", diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index 43e92c59e2..72d613afa3 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -571,24 +571,24 @@ class LoadPlaceholder(PlaceholderItem): self.parent_in_hierarchy(container) -def build_workfile_template(): +def build_workfile_template(*args): builder = MayaTemplateLoader(registered_host()) builder.build_template() -def update_workfile_template(): +def update_workfile_template(*args): builder = MayaTemplateLoader(registered_host()) builder.update_build_template() -def create_placeholder(): +def create_placeholder(*args): host = registered_host() builder = MayaTemplateLoader(host) window = WorkfileBuildPlaceholderDialog(host, builder) window.exec_() -def update_placeholder(): +def update_placeholder(*args): host = registered_host() builder = MayaTemplateLoader(host) placeholder_items_by_id = { From b2a59336eca51bbc163e66a830238e57c3671d3b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 17:58:20 +0200 Subject: [PATCH 290/513] use attributes from builder --- openpype/hosts/maya/api/new_template_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index 72d613afa3..80f62b8759 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -272,8 +272,8 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): if ignore_repre_ids is None: ignore_repre_ids = set() - current_asset_doc = self.current_asset_doc - linked_assets = self.linked_assets + current_asset_doc = self.builder.current_asset_doc + linked_assets = self.builder.linked_asset_docs loader_name = placeholder.data["loader"] loader_args = placeholder.data["loader_args"] From bd5d7ca4a6d53bdc23d573a9b190c70a077b0437 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 17:58:51 +0200 Subject: [PATCH 291/513] added more specific attributes --- .../pipeline/workfile/new_template_loader.py | 82 ++++++++++++++++--- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index b97e48dcba..b23f03b0df 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -5,7 +5,10 @@ from abc import ABCMeta, abstractmethod import six -from openpype.client import get_asset_by_name +from openpype.client import ( + get_asset_by_name, + get_linked_assets, +) from openpype.settings import get_project_settings from openpype.host import HostBase from openpype.lib import ( @@ -67,11 +70,50 @@ class AbstractTemplateLoader: self._loaders_by_name = None self._creators_by_name = None - self.current_asset = asset_name - self.project_name = project_name - self.task_name = task_name - self.current_asset_doc = current_asset_doc - self.task_type = task_type + self._current_asset_doc = None + self._linked_asset_docs = None + self._task_type = None + + @property + def project_name(self): + return legacy_io.active_project() + + @property + def current_asset_name(self): + return legacy_io.Session["AVALON_ASSET"] + + @property + def current_task_name(self): + return legacy_io.Session["AVALON_TASK"] + + @property + def current_asset_doc(self): + if self._current_asset_doc is None: + self._current_asset_doc = get_asset_by_name( + self.project_name, self.current_asset_name + ) + return self._current_asset_doc + + @property + def linked_asset_docs(self): + if self._linked_asset_docs is None: + self._linked_asset_docs = get_linked_assets( + self.current_asset_doc + ) + return self._linked_asset_docs + + @property + def current_task_type(self): + asset_doc = self.current_asset_doc + if not asset_doc: + return None + return ( + asset_doc + .get("data", {}) + .get("tasks", {}) + .get(self.current_task_name, {}) + .get("type") + ) def get_placeholder_plugin_classes(self): """Get placeholder plugin classes that can be used to build template. @@ -121,6 +163,11 @@ class AbstractTemplateLoader: self._placeholder_plugins = None self._loaders_by_name = None self._creators_by_name = None + + self._current_asset_doc = None + self._linked_asset_docs = None + self._task_type = None + self.clear_shared_data() self.clear_shared_populate_data() @@ -432,10 +479,18 @@ class AbstractTemplateLoader: placeholder_plugin.populate_placeholder(placeholder) except Exception as exc: - placeholder.set_error(exc) + self.log.warning( + ( + "Failed to process placeholder {} with plugin {}" + ).format( + placeholder.scene_identifier, + placeholder_plugin.__class__.__name__ + ), + exc_info=True + ) + placeholder.set_failed(exc) - else: - placeholder.set_finished() + placeholder.set_finished() # Clear shared data before getting new placeholders self.clear_shared_populate_data() @@ -467,10 +522,10 @@ class AbstractTemplateLoader: ) def get_template_path(self): - project_name = self.project_name host_name = self.host_name - task_name = self.task_name - task_type = self.task_type + project_name = self.project_name + task_name = self.current_task_name + task_type = self.current_task_type build_profiles = self._get_build_profiles() profile = filter_profiles( @@ -872,6 +927,9 @@ class PlaceholderItem(object): self._state = 2 + def set_failed(self, exception): + self.add_error(str(exception)) + def add_error(self, error): """Set placeholder item as failed and mark it as finished.""" From 419812241c91f8d9b1dad18a36937cad0fe152fe Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 18:00:57 +0200 Subject: [PATCH 292/513] removed unused code --- .../hosts/maya/api/new_template_builder.py | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/new_template_builder.py index 80f62b8759..05f6a8551a 100644 --- a/openpype/hosts/maya/api/new_template_builder.py +++ b/openpype/hosts/maya/api/new_template_builder.py @@ -49,61 +49,8 @@ class MayaTemplateLoader(AbstractTemplateLoader): cmds.setAttr(PLACEHOLDER_SET + ".hiddenInOutliner", True) - # This should be handled by creators - # for set_name in cmds.listSets(allSets=True): - # if ( - # cmds.objExists(set_name) - # and cmds.attributeQuery('id', node=set_name, exists=True) - # and cmds.getAttr(set_name + '.id') == 'pyblish.avalon.instance' - # ): - # if cmds.attributeQuery('asset', node=set_name, exists=True): - # cmds.setAttr( - # set_name + '.asset', - # legacy_io.Session['AVALON_ASSET'], type='string' - # ) - return True - # def template_already_imported(self, err_msg): - # clearButton = "Clear scene and build" - # updateButton = "Update template" - # abortButton = "Abort" - # - # title = "Scene already builded" - # message = ( - # "It's seems a template was already build for this scene.\n" - # "Error message reveived :\n\n\"{}\"".format(err_msg)) - # buttons = [clearButton, updateButton, abortButton] - # defaultButton = clearButton - # cancelButton = abortButton - # dismissString = abortButton - # answer = cmds.confirmDialog( - # t=title, - # m=message, - # b=buttons, - # db=defaultButton, - # cb=cancelButton, - # ds=dismissString) - # - # if answer == clearButton: - # cmds.file(newFile=True, force=True) - # self.import_template(self.template_path) - # self.populate_template() - # elif answer == updateButton: - # self.update_missing_containers() - # elif answer == abortButton: - # return - - # def get_loaded_containers_by_id(self): - # try: - # containers = cmds.sets("AVALON_CONTAINERS", q=True) - # except ValueError: - # return None - # - # return [ - # cmds.getAttr(container + '.representation') - # for container in containers] - class MayaLoadPlaceholderPlugin(PlaceholderPlugin): identifier = "maya.load" From 486d6636a994da49c35113337b4fa62c3e1d2071 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 18:05:17 +0200 Subject: [PATCH 293/513] renamed file 'new_template_builder' to 'workfile_template_builder' --- openpype/hosts/maya/api/menu.py | 2 +- openpype/hosts/maya/api/pipeline.py | 2 +- .../{new_template_builder.py => workfile_template_builder.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename openpype/hosts/maya/api/{new_template_builder.py => workfile_template_builder.py} (100%) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index cc9a17fd72..e20f29049b 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -14,7 +14,7 @@ from openpype.hosts.maya.api import lib, lib_rendersettings from .lib import get_main_window, IS_HEADLESS from .commands import reset_frame_range -from .new_template_builder import ( +from .workfile_template_builder import ( create_placeholder, update_placeholder, build_workfile_template, diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 2492e75b36..c47e34aebc 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -35,7 +35,7 @@ from openpype.hosts.maya import MAYA_ROOT_DIR from openpype.hosts.maya.lib import copy_workspace_mel from . import menu, lib -from .new_template_builder import MayaLoadPlaceholderPlugin +from .workfile_template_builder import MayaLoadPlaceholderPlugin from .workio import ( open_file, save_file, diff --git a/openpype/hosts/maya/api/new_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py similarity index 100% rename from openpype/hosts/maya/api/new_template_builder.py rename to openpype/hosts/maya/api/workfile_template_builder.py From a3b6d9645e7a0b93d359b4304e8a96381cd373ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 18:05:27 +0200 Subject: [PATCH 294/513] removed legacy workfile template builder --- .../hosts/maya/api/lib_template_builder.py | 253 ------------------ openpype/hosts/maya/api/template_loader.py | 252 ----------------- 2 files changed, 505 deletions(-) delete mode 100644 openpype/hosts/maya/api/lib_template_builder.py delete mode 100644 openpype/hosts/maya/api/template_loader.py diff --git a/openpype/hosts/maya/api/lib_template_builder.py b/openpype/hosts/maya/api/lib_template_builder.py deleted file mode 100644 index 34a8450a26..0000000000 --- a/openpype/hosts/maya/api/lib_template_builder.py +++ /dev/null @@ -1,253 +0,0 @@ -import json -from collections import OrderedDict -import maya.cmds as cmds - -import qargparse -from openpype.tools.utils.widgets import OptionDialog -from .lib import get_main_window, imprint - -# To change as enum -build_types = ["context_asset", "linked_asset", "all_assets"] - - -def get_placeholder_attributes(node): - return { - attr: cmds.getAttr("{}.{}".format(node, attr)) - for attr in cmds.listAttr(node, userDefined=True)} - - -def delete_placeholder_attributes(node): - ''' - function to delete all extra placeholder attributes - ''' - extra_attributes = get_placeholder_attributes(node) - for attribute in extra_attributes: - cmds.deleteAttr(node + '.' + attribute) - - -def create_placeholder(): - args = placeholder_window() - - if not args: - return # operation canceled, no locator created - - # custom arg parse to force empty data query - # and still imprint them on placeholder - # and getting items when arg is of type Enumerator - options = create_options(args) - - # create placeholder name dynamically from args and options - placeholder_name = create_placeholder_name(args, options) - - selection = cmds.ls(selection=True) - if not selection: - raise ValueError("Nothing is selected") - - placeholder = cmds.spaceLocator(name=placeholder_name)[0] - - # get the long name of the placeholder (with the groups) - placeholder_full_name = cmds.ls(selection[0], long=True)[ - 0] + '|' + placeholder.replace('|', '') - - if selection: - cmds.parent(placeholder, selection[0]) - - imprint(placeholder_full_name, options) - - # Some tweaks because imprint force enums to to default value so we get - # back arg read and force them to attributes - imprint_enum(placeholder_full_name, args) - - # Add helper attributes to keep placeholder info - cmds.addAttr( - placeholder_full_name, - longName="parent", - hidden=True, - dataType="string" - ) - cmds.addAttr( - placeholder_full_name, - longName="index", - hidden=True, - attributeType="short", - defaultValue=-1 - ) - - cmds.setAttr(placeholder_full_name + '.parent', "", type="string") - - -def create_placeholder_name(args, options): - placeholder_builder_type = [ - arg.read() for arg in args if 'builder_type' in str(arg) - ][0] - placeholder_family = options['family'] - placeholder_name = placeholder_builder_type.split('_') - - # add famlily in any - if placeholder_family: - placeholder_name.insert(1, placeholder_family) - - # add loader arguments if any - if options['loader_args']: - pos = 2 - loader_args = options['loader_args'].replace('\'', '\"') - loader_args = json.loads(loader_args) - values = [v for v in loader_args.values()] - for i in range(len(values)): - placeholder_name.insert(i + pos, values[i]) - - placeholder_name = '_'.join(placeholder_name) - - return placeholder_name.capitalize() - - -def update_placeholder(): - placeholder = cmds.ls(selection=True) - if len(placeholder) == 0: - raise ValueError("No node selected") - if len(placeholder) > 1: - raise ValueError("Too many selected nodes") - placeholder = placeholder[0] - - args = placeholder_window(get_placeholder_attributes(placeholder)) - - if not args: - return # operation canceled - - # delete placeholder attributes - delete_placeholder_attributes(placeholder) - - options = create_options(args) - - imprint(placeholder, options) - imprint_enum(placeholder, args) - - cmds.addAttr( - placeholder, - longName="parent", - hidden=True, - dataType="string" - ) - cmds.addAttr( - placeholder, - longName="index", - hidden=True, - attributeType="short", - defaultValue=-1 - ) - - cmds.setAttr(placeholder + '.parent', '', type="string") - - -def create_options(args): - options = OrderedDict() - for arg in args: - if not type(arg) == qargparse.Separator: - options[str(arg)] = arg._data.get("items") or arg.read() - return options - - -def imprint_enum(placeholder, args): - """ - Imprint method doesn't act properly with enums. - Replacing the functionnality with this for now - """ - enum_values = {str(arg): arg.read() - for arg in args if arg._data.get("items")} - string_to_value_enum_table = { - build: i for i, build - in enumerate(build_types)} - for key, value in enum_values.items(): - cmds.setAttr( - placeholder + "." + key, - string_to_value_enum_table[value]) - - -def placeholder_window(options=None): - options = options or dict() - dialog = OptionDialog(parent=get_main_window()) - dialog.setWindowTitle("Create Placeholder") - - args = [ - qargparse.Separator("Main attributes"), - qargparse.Enum( - "builder_type", - label="Asset Builder Type", - default=options.get("builder_type", 0), - items=build_types, - help="""Asset Builder Type -Builder type describe what template loader will look for. -context_asset : Template loader will look for subsets of -current context asset (Asset bob will find asset) -linked_asset : Template loader will look for assets linked -to current context asset. -Linked asset are looked in avalon database under field "inputLinks" -""" - ), - qargparse.String( - "family", - default=options.get("family", ""), - label="OpenPype Family", - placeholder="ex: model, look ..."), - qargparse.String( - "representation", - default=options.get("representation", ""), - label="OpenPype Representation", - placeholder="ex: ma, abc ..."), - qargparse.String( - "loader", - default=options.get("loader", ""), - label="Loader", - placeholder="ex: ReferenceLoader, LightLoader ...", - help="""Loader -Defines what openpype loader will be used to load assets. -Useable loader depends on current host's loader list. -Field is case sensitive. -"""), - qargparse.String( - "loader_args", - default=options.get("loader_args", ""), - label="Loader Arguments", - placeholder='ex: {"camera":"persp", "lights":True}', - help="""Loader -Defines a dictionnary of arguments used to load assets. -Useable arguments depend on current placeholder Loader. -Field should be a valid python dict. Anything else will be ignored. -"""), - qargparse.Integer( - "order", - default=options.get("order", 0), - min=0, - max=999, - label="Order", - placeholder="ex: 0, 100 ... (smallest order loaded first)", - help="""Order -Order defines asset loading priority (0 to 999) -Priority rule is : "lowest is first to load"."""), - qargparse.Separator( - "Optional attributes"), - qargparse.String( - "asset", - default=options.get("asset", ""), - label="Asset filter", - placeholder="regex filtering by asset name", - help="Filtering assets by matching field regex to asset's name"), - qargparse.String( - "subset", - default=options.get("subset", ""), - label="Subset filter", - placeholder="regex filtering by subset name", - help="Filtering assets by matching field regex to subset's name"), - qargparse.String( - "hierarchy", - default=options.get("hierarchy", ""), - label="Hierarchy filter", - placeholder="regex filtering by asset's hierarchy", - help="Filtering assets by matching field asset's hierarchy") - ] - dialog.create(args) - - if not dialog.exec_(): - return None - - return args diff --git a/openpype/hosts/maya/api/template_loader.py b/openpype/hosts/maya/api/template_loader.py deleted file mode 100644 index ecffafc93d..0000000000 --- a/openpype/hosts/maya/api/template_loader.py +++ /dev/null @@ -1,252 +0,0 @@ -import re -from maya import cmds - -from openpype.client import get_representations -from openpype.pipeline import legacy_io -from openpype.pipeline.workfile.abstract_template_loader import ( - AbstractPlaceholder, - AbstractTemplateLoader -) -from openpype.pipeline.workfile.build_template_exceptions import ( - TemplateAlreadyImported -) - -PLACEHOLDER_SET = 'PLACEHOLDERS_SET' - - -class MayaTemplateLoader(AbstractTemplateLoader): - """Concrete implementation of AbstractTemplateLoader for maya - """ - - def import_template(self, path): - """Import template into current scene. - Block if a template is already loaded. - Args: - path (str): A path to current template (usually given by - get_template_path implementation) - Returns: - bool: Wether the template was succesfully imported or not - """ - if cmds.objExists(PLACEHOLDER_SET): - raise TemplateAlreadyImported( - "Build template already loaded\n" - "Clean scene if needed (File > New Scene)") - - cmds.sets(name=PLACEHOLDER_SET, empty=True) - self.new_nodes = cmds.file(path, i=True, returnNewNodes=True) - cmds.setAttr(PLACEHOLDER_SET + '.hiddenInOutliner', True) - - for set in cmds.listSets(allSets=True): - if (cmds.objExists(set) and - cmds.attributeQuery('id', node=set, exists=True) and - cmds.getAttr(set + '.id') == 'pyblish.avalon.instance'): - if cmds.attributeQuery('asset', node=set, exists=True): - cmds.setAttr( - set + '.asset', - legacy_io.Session['AVALON_ASSET'], type='string' - ) - - return True - - def template_already_imported(self, err_msg): - clearButton = "Clear scene and build" - updateButton = "Update template" - abortButton = "Abort" - - title = "Scene already builded" - message = ( - "It's seems a template was already build for this scene.\n" - "Error message reveived :\n\n\"{}\"".format(err_msg)) - buttons = [clearButton, updateButton, abortButton] - defaultButton = clearButton - cancelButton = abortButton - dismissString = abortButton - answer = cmds.confirmDialog( - t=title, - m=message, - b=buttons, - db=defaultButton, - cb=cancelButton, - ds=dismissString) - - if answer == clearButton: - cmds.file(newFile=True, force=True) - self.import_template(self.template_path) - self.populate_template() - elif answer == updateButton: - self.update_missing_containers() - elif answer == abortButton: - return - - @staticmethod - def get_template_nodes(): - attributes = cmds.ls('*.builder_type', long=True) - return [attribute.rpartition('.')[0] for attribute in attributes] - - def get_loaded_containers_by_id(self): - try: - containers = cmds.sets("AVALON_CONTAINERS", q=True) - except ValueError: - return None - - return [ - cmds.getAttr(container + '.representation') - for container in containers] - - -class MayaPlaceholder(AbstractPlaceholder): - """Concrete implementation of AbstractPlaceholder for maya - """ - - optional_keys = {'asset', 'subset', 'hierarchy'} - - def get_data(self, node): - user_data = dict() - for attr in self.required_keys.union(self.optional_keys): - attribute_name = '{}.{}'.format(node, attr) - if not cmds.attributeQuery(attr, node=node, exists=True): - print("{} not found".format(attribute_name)) - continue - user_data[attr] = cmds.getAttr( - attribute_name, - asString=True) - user_data['parent'] = ( - cmds.getAttr(node + '.parent', asString=True) - or node.rpartition('|')[0] - or "" - ) - user_data['node'] = node - if user_data['parent']: - siblings = cmds.listRelatives(user_data['parent'], children=True) - else: - siblings = cmds.ls(assemblies=True) - node_shortname = user_data['node'].rpartition('|')[2] - current_index = cmds.getAttr(node + '.index', asString=True) - user_data['index'] = ( - current_index if current_index >= 0 - else siblings.index(node_shortname)) - - self.data = user_data - - def parent_in_hierarchy(self, containers): - """Parent loaded container to placeholder's parent - ie : Set loaded content as placeholder's sibling - Args: - containers (String): Placeholder loaded containers - """ - if not containers: - return - - roots = cmds.sets(containers, q=True) - nodes_to_parent = [] - for root in roots: - if root.endswith("_RN"): - refRoot = cmds.referenceQuery(root, n=True)[0] - refRoot = cmds.listRelatives(refRoot, parent=True) or [refRoot] - nodes_to_parent.extend(refRoot) - elif root in cmds.listSets(allSets=True): - if not cmds.sets(root, q=True): - return - else: - continue - else: - nodes_to_parent.append(root) - - if self.data['parent']: - cmds.parent(nodes_to_parent, self.data['parent']) - # Move loaded nodes to correct index in outliner hierarchy - placeholder_node = self.data['node'] - placeholder_form = cmds.xform( - placeholder_node, - q=True, - matrix=True, - worldSpace=True - ) - for node in set(nodes_to_parent): - cmds.reorder(node, front=True) - cmds.reorder(node, relative=self.data['index']) - cmds.xform(node, matrix=placeholder_form, ws=True) - - holding_sets = cmds.listSets(object=placeholder_node) - if not holding_sets: - return - for holding_set in holding_sets: - cmds.sets(roots, forceElement=holding_set) - - def clean(self): - """Hide placeholder, parent them to root - add them to placeholder set and register placeholder's parent - to keep placeholder info available for future use - """ - node = self.data['node'] - if self.data['parent']: - cmds.setAttr(node + '.parent', self.data['parent'], type='string') - if cmds.getAttr(node + '.index') < 0: - cmds.setAttr(node + '.index', self.data['index']) - - holding_sets = cmds.listSets(object=node) - if holding_sets: - for set in holding_sets: - cmds.sets(node, remove=set) - - if cmds.listRelatives(node, p=True): - node = cmds.parent(node, world=True)[0] - cmds.sets(node, addElement=PLACEHOLDER_SET) - cmds.hide(node) - cmds.setAttr(node + '.hiddenInOutliner', True) - - def get_representations(self, current_asset_doc, linked_asset_docs): - project_name = legacy_io.active_project() - - builder_type = self.data["builder_type"] - if builder_type == "context_asset": - context_filters = { - "asset": [current_asset_doc["name"]], - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representations": [self.data["representation"]], - "family": [self.data["family"]] - } - - elif builder_type != "linked_asset": - context_filters = { - "asset": [re.compile(self.data["asset"])], - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representation": [self.data["representation"]], - "family": [self.data["family"]] - } - - else: - asset_regex = re.compile(self.data["asset"]) - linked_asset_names = [] - for asset_doc in linked_asset_docs: - asset_name = asset_doc["name"] - if asset_regex.match(asset_name): - linked_asset_names.append(asset_name) - - context_filters = { - "asset": linked_asset_names, - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representation": [self.data["representation"]], - "family": [self.data["family"]], - } - - return list(get_representations( - project_name, - context_filters=context_filters - )) - - def err_message(self): - return ( - "Error while trying to load a representation.\n" - "Either the subset wasn't published or the template is malformed." - "\n\n" - "Builder was looking for :\n{attributes}".format( - attributes="\n".join([ - "{}: {}".format(key.title(), value) - for key, value in self.data.items()] - ) - ) - ) From 778f2c09ad13675981076c6eef601447da41d591 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 18:09:18 +0200 Subject: [PATCH 295/513] renamed 'update_template' to 'rebuild_template' --- openpype/hosts/maya/api/workfile_template_builder.py | 4 ++-- openpype/pipeline/workfile/new_template_loader.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 05f6a8551a..98da18bba1 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -135,7 +135,7 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): for container in containers } self.builder.set_shared_populate_data( - "loaded_representation_ids" + "loaded_representation_ids", loaded_representation_ids ) return loaded_representation_ids @@ -525,7 +525,7 @@ def build_workfile_template(*args): def update_workfile_template(*args): builder = MayaTemplateLoader(registered_host()) - builder.update_build_template() + builder.rebuild_template() def create_placeholder(*args): diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index b23f03b0df..47d84d4ff7 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -350,7 +350,7 @@ class AbstractTemplateLoader: self.import_template(template_path) self.populate_scene_placeholders(level_limit) - def update_template(self): + def rebuild_template(self): """Go through existing placeholders in scene and update them. This could not make sense for all plugin types so this is optional From abbe7b7a3609dcd5c33bcbd8eff1e19853d0b495 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 19:15:49 +0200 Subject: [PATCH 296/513] implemented function 'get_contexts_for_repre_docs' to get representation contexts from already queried representations --- openpype/pipeline/load/utils.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index 83b904e4a7..22e823bd3b 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -87,13 +87,20 @@ def get_repres_contexts(representation_ids, dbcon=None): if not dbcon: dbcon = legacy_io - contexts = {} if not representation_ids: - return contexts + return {} project_name = dbcon.active_project() repre_docs = get_representations(project_name, representation_ids) + return get_contexts_for_repre_docs(project_name, repre_docs) + + +def get_contexts_for_repre_docs(project_name, repre_docs): + contexts = {} + if not repre_docs: + return contexts + repre_docs_by_id = {} version_ids = set() for repre_doc in repre_docs: From 81b9b16a5c2eb3c0e1845262a1e2747f8bc9e6d5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 19:16:14 +0200 Subject: [PATCH 297/513] extracted loading specific logic into load mixin --- .../maya/api/workfile_template_builder.py | 228 +------------- .../pipeline/workfile/new_template_loader.py | 283 +++++++++++++++++- 2 files changed, 292 insertions(+), 219 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 98da18bba1..14f1f284fd 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -1,11 +1,8 @@ -import re import json from maya import cmds -from openpype.client import get_representations -from openpype.lib import attribute_definitions -from openpype.pipeline import legacy_io, registered_host +from openpype.pipeline import registered_host from openpype.pipeline.workfile.build_template_exceptions import ( TemplateAlreadyImported ) @@ -13,6 +10,7 @@ from openpype.pipeline.workfile.new_template_loader import ( AbstractTemplateLoader, PlaceholderPlugin, PlaceholderItem, + PlaceholderLoadMixin, ) from openpype.tools.workfile_template_build import ( WorkfileBuildPlaceholderDialog, @@ -52,7 +50,7 @@ class MayaTemplateLoader(AbstractTemplateLoader): return True -class MayaLoadPlaceholderPlugin(PlaceholderPlugin): +class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): identifier = "maya.load" label = "Maya load" @@ -203,190 +201,27 @@ class MayaLoadPlaceholderPlugin(PlaceholderPlugin): # TODO do data validations and maybe updgrades if are invalid output.append( - LoadPlaceholder(node_name, placeholder_data, self) + LoadPlaceholderItem(node_name, placeholder_data, self) ) return output def populate_placeholder(self, placeholder): - self._populate_placeholder(placeholder) + self.populate_load_placeholder(placeholder) def update_template_placeholder(self, placeholder): repre_ids = self._get_loaded_repre_ids() - self._populate_placeholder(placeholder, repre_ids) - - def _populate_placeholder(self, placeholder, ignore_repre_ids=None): - if ignore_repre_ids is None: - ignore_repre_ids = set() - - current_asset_doc = self.builder.current_asset_doc - linked_assets = self.builder.linked_asset_docs - loader_name = placeholder.data["loader"] - loader_args = placeholder.data["loader_args"] - - # TODO check loader existence - placeholder_representations = placeholder.get_representations( - current_asset_doc, - linked_assets - ) - - if not placeholder_representations: - self.log.info(( - "There's no representation for this placeholder: {}" - ).format(placeholder.scene_identifier)) - return - - loaders_by_name = self.builder.get_loaders_by_name() - for representation in placeholder_representations: - repre_id = str(representation["_id"]) - if repre_id in ignore_repre_ids: - continue - - repre_context = representation["context"] - self.log.info( - "Loading {} from {} with loader {}\n" - "Loader arguments used : {}".format( - repre_context["subset"], - repre_context["asset"], - loader_name, - loader_args - ) - ) - try: - container = self.load( - placeholder, loaders_by_name, representation) - except Exception: - placeholder.load_failed(representation) - - else: - placeholder.load_succeed(container) - placeholder.clean() + self.populate_load_placeholder(placeholder, repre_ids) def get_placeholder_options(self, options=None): - loaders_by_name = self.builder.get_loaders_by_name() - loader_items = [ - (loader_name, loader.label or loader_name) - for loader_name, loader in loaders_by_name.items() - ] - - loader_items = list(sorted(loader_items, key=lambda i: i[0])) - options = options or {} - return [ - attribute_definitions.UISeparatorDef(), - attribute_definitions.UILabelDef("Main attributes"), - attribute_definitions.UISeparatorDef(), - - attribute_definitions.EnumDef( - "builder_type", - label="Asset Builder Type", - default=options.get("builder_type"), - items=[ - ("context_asset", "Current asset"), - ("linked_asset", "Linked assets"), - ("all_assets", "All assets") - ], - tooltip=( - "Asset Builder Type\n" - "\nBuilder type describe what template loader will look" - " for." - "\ncontext_asset : Template loader will look for subsets" - " of current context asset (Asset bob will find asset)" - "\nlinked_asset : Template loader will look for assets" - " linked to current context asset." - "\nLinked asset are looked in database under" - " field \"inputLinks\"" - ) - ), - attribute_definitions.TextDef( - "family", - label="Family", - default=options.get("family"), - placeholder="model, look, ..." - ), - attribute_definitions.TextDef( - "representation", - label="Representation name", - default=options.get("representation"), - placeholder="ma, abc, ..." - ), - attribute_definitions.EnumDef( - "loader", - label="Loader", - default=options.get("loader"), - items=loader_items, - tooltip=( - "Loader" - "\nDefines what OpenPype loader will be used to" - " load assets." - "\nUseable loader depends on current host's loader list." - "\nField is case sensitive." - ) - ), - attribute_definitions.TextDef( - "loader_args", - label="Loader Arguments", - default=options.get("loader_args"), - placeholder='{"camera":"persp", "lights":True}', - tooltip=( - "Loader" - "\nDefines a dictionnary of arguments used to load assets." - "\nUseable arguments depend on current placeholder Loader." - "\nField should be a valid python dict." - " Anything else will be ignored." - ) - ), - attribute_definitions.NumberDef( - "order", - label="Order", - default=options.get("order") or 0, - decimals=0, - minimum=0, - maximum=999, - tooltip=( - "Order" - "\nOrder defines asset loading priority (0 to 999)" - "\nPriority rule is : \"lowest is first to load\"." - ) - ), - attribute_definitions.UISeparatorDef(), - attribute_definitions.UILabelDef("Optional attributes"), - attribute_definitions.UISeparatorDef(), - attribute_definitions.TextDef( - "asset", - label="Asset filter", - default=options.get("asset"), - placeholder="regex filtering by asset name", - tooltip=( - "Filtering assets by matching field regex to asset's name" - ) - ), - attribute_definitions.TextDef( - "subset", - label="Subset filter", - default=options.get("subset"), - placeholder="regex filtering by subset name", - tooltip=( - "Filtering assets by matching field regex to subset's name" - ) - ), - attribute_definitions.TextDef( - "hierarchy", - label="Hierarchy filter", - default=options.get("hierarchy"), - placeholder="regex filtering by asset's hierarchy", - tooltip=( - "Filtering assets by matching field asset's hierarchy" - ) - ) - ] + return self.get_load_plugin_options(self, options) -class LoadPlaceholder(PlaceholderItem): - """Concrete implementation of AbstractPlaceholder for maya - """ +class LoadPlaceholderItem(PlaceholderItem): + """Concrete implementation of PlaceholderItem for Maya load plugin.""" def __init__(self, *args, **kwargs): - super(LoadPlaceholder, self).__init__(*args, **kwargs) + super(LoadPlaceholderItem, self).__init__(*args, **kwargs) self._failed_representations = [] def parent_in_hierarchy(self, container): @@ -457,49 +292,6 @@ class LoadPlaceholder(PlaceholderItem): cmds.hide(node) cmds.setAttr(node + ".hiddenInOutliner", True) - def get_representations(self, current_asset_doc, linked_asset_docs): - project_name = legacy_io.active_project() - - builder_type = self.data["builder_type"] - if builder_type == "context_asset": - context_filters = { - "asset": [current_asset_doc["name"]], - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representations": [self.data["representation"]], - "family": [self.data["family"]] - } - - elif builder_type != "linked_asset": - context_filters = { - "asset": [re.compile(self.data["asset"])], - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representation": [self.data["representation"]], - "family": [self.data["family"]] - } - - else: - asset_regex = re.compile(self.data["asset"]) - linked_asset_names = [] - for asset_doc in linked_asset_docs: - asset_name = asset_doc["name"] - if asset_regex.match(asset_name): - linked_asset_names.append(asset_name) - - context_filters = { - "asset": linked_asset_names, - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representation": [self.data["representation"]], - "family": [self.data["family"]], - } - - return list(get_representations( - project_name, - context_filters=context_filters - )) - def get_errors(self): if not self._failed_representations: return [] diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 47d84d4ff7..921cc39ba9 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -1,4 +1,5 @@ import os +import re import collections import copy from abc import ABCMeta, abstractmethod @@ -8,6 +9,7 @@ import six from openpype.client import ( get_asset_by_name, get_linked_assets, + get_representations, ) from openpype.settings import get_project_settings from openpype.host import HostBase @@ -15,10 +17,15 @@ from openpype.lib import ( Logger, StringTemplate, filter_profiles, + attribute_definitions, ) from openpype.lib.attribute_definitions import get_attributes_keys from openpype.pipeline import legacy_io, Anatomy -from openpype.pipeline.load import get_loaders_by_name +from openpype.pipeline.load import ( + get_loaders_by_name, + get_contexts_for_repre_docs, + load_with_repre_context, +) from openpype.pipeline.create import get_legacy_creator_by_name from .build_template_exceptions import ( @@ -942,3 +949,277 @@ class PlaceholderItem(object): """ return self._errors + + +class PlaceholderLoadMixin(object): + """Mixin prepared for loading placeholder plugins. + + Implementation prepares options for placeholders with + 'get_load_plugin_options'. + + For placeholder population is implemented 'populate_load_placeholder'. + + Requires that PlaceholderItem has implemented methods: + - 'load_failed' - called when loading of one representation failed + - 'load_succeed' - called when loading of one representation succeeded + - 'clean' - called when placeholder processing finished + """ + + def get_load_plugin_options(self, options=None): + """Unified attribute definitions for load placeholder. + + Common function for placeholder plugins used for loading of + repsentations. + + Args: + plugin (PlaceholderPlugin): Plugin used for loading of + representations. + options (Dict[str, Any]): Already available options which are used + as defaults for attributes. + + Returns: + List[AbtractAttrDef]: Attribute definitions common for load + plugins. + """ + + loaders_by_name = self.builder.get_loaders_by_name() + loader_items = [ + (loader_name, loader.label or loader_name) + for loader_name, loader in loaders_by_name.items() + ] + + loader_items = list(sorted(loader_items, key=lambda i: i[1])) + options = options or {} + return [ + attribute_definitions.UISeparatorDef(), + attribute_definitions.UILabelDef("Main attributes"), + attribute_definitions.UISeparatorDef(), + + attribute_definitions.EnumDef( + "builder_type", + label="Asset Builder Type", + default=options.get("builder_type"), + items=[ + ("context_asset", "Current asset"), + ("linked_asset", "Linked assets"), + ("all_assets", "All assets") + ], + tooltip=( + "Asset Builder Type\n" + "\nBuilder type describe what template loader will look" + " for." + "\ncontext_asset : Template loader will look for subsets" + " of current context asset (Asset bob will find asset)" + "\nlinked_asset : Template loader will look for assets" + " linked to current context asset." + "\nLinked asset are looked in database under" + " field \"inputLinks\"" + ) + ), + attribute_definitions.TextDef( + "family", + label="Family", + default=options.get("family"), + placeholder="model, look, ..." + ), + attribute_definitions.TextDef( + "representation", + label="Representation name", + default=options.get("representation"), + placeholder="ma, abc, ..." + ), + attribute_definitions.EnumDef( + "loader", + label="Loader", + default=options.get("loader"), + items=loader_items, + tooltip=( + "Loader" + "\nDefines what OpenPype loader will be used to" + " load assets." + "\nUseable loader depends on current host's loader list." + "\nField is case sensitive." + ) + ), + attribute_definitions.TextDef( + "loader_args", + label="Loader Arguments", + default=options.get("loader_args"), + placeholder='{"camera":"persp", "lights":True}', + tooltip=( + "Loader" + "\nDefines a dictionnary of arguments used to load assets." + "\nUseable arguments depend on current placeholder Loader." + "\nField should be a valid python dict." + " Anything else will be ignored." + ) + ), + attribute_definitions.NumberDef( + "order", + label="Order", + default=options.get("order") or 0, + decimals=0, + minimum=0, + maximum=999, + tooltip=( + "Order" + "\nOrder defines asset loading priority (0 to 999)" + "\nPriority rule is : \"lowest is first to load\"." + ) + ), + attribute_definitions.UISeparatorDef(), + attribute_definitions.UILabelDef("Optional attributes"), + attribute_definitions.UISeparatorDef(), + attribute_definitions.TextDef( + "asset", + label="Asset filter", + default=options.get("asset"), + placeholder="regex filtering by asset name", + tooltip=( + "Filtering assets by matching field regex to asset's name" + ) + ), + attribute_definitions.TextDef( + "subset", + label="Subset filter", + default=options.get("subset"), + placeholder="regex filtering by subset name", + tooltip=( + "Filtering assets by matching field regex to subset's name" + ) + ), + attribute_definitions.TextDef( + "hierarchy", + label="Hierarchy filter", + default=options.get("hierarchy"), + placeholder="regex filtering by asset's hierarchy", + tooltip=( + "Filtering assets by matching field asset's hierarchy" + ) + ) + ] + + def parse_loader_args(self, loader_args): + """Helper function to parse string of loader arugments. + + Empty dictionary is returned if conversion fails. + + Args: + loader_args (str): Loader args filled by user. + + Returns: + Dict[str, Any]: Parsed arguments used as dictionary. + """ + + if not loader_args: + return {} + + try: + parsed_args = eval(loader_args) + if isinstance(parsed_args, dict): + return parsed_args + + except Exception as err: + print( + "Error while parsing loader arguments '{}'.\n{}: {}\n\n" + "Continuing with default arguments. . .".format( + loader_args, err.__class__.__name__, err)) + + return {} + + def get_representations(self, placeholder): + project_name = self.builder.project_name + current_asset_doc = self.builder.current_asset_doc + linked_asset_docs = self.builder.linked_asset_docs + + builder_type = placeholder.data["builder_type"] + if builder_type == "context_asset": + context_filters = { + "asset": [current_asset_doc["name"]], + "subset": [re.compile(placeholder.data["subset"])], + "hierarchy": [re.compile(placeholder.data["hierarchy"])], + "representations": [placeholder.data["representation"]], + "family": [placeholder.data["family"]] + } + + elif builder_type != "linked_asset": + context_filters = { + "asset": [re.compile(placeholder.data["asset"])], + "subset": [re.compile(placeholder.data["subset"])], + "hierarchy": [re.compile(placeholder.data["hierarchy"])], + "representation": [placeholder.data["representation"]], + "family": [placeholder.data["family"]] + } + + else: + asset_regex = re.compile(placeholder.data["asset"]) + linked_asset_names = [] + for asset_doc in linked_asset_docs: + asset_name = asset_doc["name"] + if asset_regex.match(asset_name): + linked_asset_names.append(asset_name) + + context_filters = { + "asset": linked_asset_names, + "subset": [re.compile(placeholder.data["subset"])], + "hierarchy": [re.compile(placeholder.data["hierarchy"])], + "representation": [placeholder.data["representation"]], + "family": [placeholder.data["family"]], + } + + return list(get_representations( + project_name, + context_filters=context_filters + )) + + def populate_load_placeholder(self, placeholder, ignore_repre_ids=None): + if ignore_repre_ids is None: + ignore_repre_ids = set() + + # TODO check loader existence + loader_name = placeholder.data["loader"] + loader_args = placeholder.data["loader_args"] + + placeholder_representations = self.get_representations(placeholder) + + filtered_representations = [] + for representation in placeholder_representations: + repre_id = str(representation["_id"]) + if repre_id not in ignore_repre_ids: + filtered_representations.append(representation) + + if not filtered_representations: + self.log.info(( + "There's no representation for this placeholder: {}" + ).format(placeholder.scene_identifier)) + return + + repre_load_contexts = get_contexts_for_repre_docs( + self.project_name, filtered_representations + ) + loaders_by_name = self.builder.get_loaders_by_name() + for repre_load_context in repre_load_contexts: + representation = repre_load_context["representation"] + repre_context = representation["context"] + self.log.info( + "Loading {} from {} with loader {}\n" + "Loader arguments used : {}".format( + repre_context["subset"], + repre_context["asset"], + loader_name, + loader_args + ) + ) + try: + container = load_with_repre_context( + loaders_by_name[loader_name], + repre_load_context, + options=self.parse_loader_args(loader_args) + ) + + except Exception: + placeholder.load_failed(representation) + + else: + placeholder.load_succeed(container) + placeholder.clean() From a2a900d18aedf1ad9e4fd87868487f24359df094 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 9 Sep 2022 19:16:28 +0200 Subject: [PATCH 298/513] fix class name change --- openpype/hosts/maya/api/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index c47e34aebc..70e6b02e4c 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -35,7 +35,7 @@ from openpype.hosts.maya import MAYA_ROOT_DIR from openpype.hosts.maya.lib import copy_workspace_mel from . import menu, lib -from .workfile_template_builder import MayaLoadPlaceholderPlugin +from .workfile_template_builder import MayaPlaceholderLoadPlugin from .workio import ( open_file, save_file, @@ -126,7 +126,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): def get_workfile_build_placeholder_plugins(self): return [ - MayaLoadPlaceholderPlugin + MayaPlaceholderLoadPlugin ] @contextlib.contextmanager From 87cb5e25b82d3ea352b17794e231924bdbb097ed Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 10 Sep 2022 04:17:04 +0000 Subject: [PATCH 299/513] [Automated] Bump version --- CHANGELOG.md | 45 ++++++++++++++++++--------------------------- openpype/version.py | 2 +- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6754f1e2e3..0ffb6a996b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,36 @@ # Changelog -## [3.14.2-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.2-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) **🆕 New features** - Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) +- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) +- Global: making collect audio plugin global [\#3679](https://github.com/pypeclub/OpenPype/pull/3679) **🚀 Enhancements** +- Flame: Adding Creator's retimed shot and handles switch [\#3826](https://github.com/pypeclub/OpenPype/pull/3826) +- Flame: OpenPype submenu to batch and media manager [\#3825](https://github.com/pypeclub/OpenPype/pull/3825) +- General: Better pixmap scaling [\#3809](https://github.com/pypeclub/OpenPype/pull/3809) - Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) - SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) -- Blender: Publisher collect workfile representation [\#3670](https://github.com/pypeclub/OpenPype/pull/3670) -- Maya: move set render settings menu entry [\#3669](https://github.com/pypeclub/OpenPype/pull/3669) -- Scene Inventory: Maya add actions to select from or to scene [\#3659](https://github.com/pypeclub/OpenPype/pull/3659) +- Kitsu: Drop 'entities root' setting. [\#3739](https://github.com/pypeclub/OpenPype/pull/3739) **🐛 Bug fixes** +- General: Fix Pattern access in client code [\#3828](https://github.com/pypeclub/OpenPype/pull/3828) +- Launcher: Skip opening last work file works for groups [\#3822](https://github.com/pypeclub/OpenPype/pull/3822) +- Maya: Publishing data key change [\#3811](https://github.com/pypeclub/OpenPype/pull/3811) +- Igniter: Fix status handling when version is already installed [\#3804](https://github.com/pypeclub/OpenPype/pull/3804) - Resolve: Addon import is Python 2 compatible [\#3798](https://github.com/pypeclub/OpenPype/pull/3798) +- Hiero: retimed clip publishing is working [\#3792](https://github.com/pypeclub/OpenPype/pull/3792) - nuke: validate write node is not failing due wrong type [\#3780](https://github.com/pypeclub/OpenPype/pull/3780) - Fix - changed format of version string in pyproject.toml [\#3777](https://github.com/pypeclub/OpenPype/pull/3777) - Ftrack status fix typo prgoress -\> progress [\#3761](https://github.com/pypeclub/OpenPype/pull/3761) - Fix version resolution [\#3757](https://github.com/pypeclub/OpenPype/pull/3757) -- Maya: `containerise` dont skip empty values [\#3674](https://github.com/pypeclub/OpenPype/pull/3674) **🔀 Refactored code** @@ -33,17 +40,19 @@ - General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) - General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) - General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) +- General: Move create project folders to pipeline [\#3768](https://github.com/pypeclub/OpenPype/pull/3768) - General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) -- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) - General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) +- Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) - Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) - Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) **Merged pull requests:** - Standalone Publisher: Ignore empty labels, then still use name like other asset models [\#3779](https://github.com/pypeclub/OpenPype/pull/3779) +- Kitsu - sync\_all\_project - add list ignore\_projects [\#3776](https://github.com/pypeclub/OpenPype/pull/3776) ## [3.14.1](https://github.com/pypeclub/OpenPype/tree/3.14.1) (2022-08-30) @@ -52,23 +61,16 @@ ### 📖 Documentation - Documentation: Few updates [\#3698](https://github.com/pypeclub/OpenPype/pull/3698) -- Documentation: Settings development [\#3660](https://github.com/pypeclub/OpenPype/pull/3660) - -**🆕 New features** - -- Webpublisher:change create flatten image into tri state [\#3678](https://github.com/pypeclub/OpenPype/pull/3678) -- Blender: validators code correction with settings and defaults [\#3662](https://github.com/pypeclub/OpenPype/pull/3662) **🚀 Enhancements** - General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) +- git: update gitignore [\#3722](https://github.com/pypeclub/OpenPype/pull/3722) - Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) - General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) - Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) - Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) - General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) -- Ftrack: Store ftrack entities on hierarchy integration to instances [\#3677](https://github.com/pypeclub/OpenPype/pull/3677) -- Blender: ops refresh manager after process events [\#3663](https://github.com/pypeclub/OpenPype/pull/3663) **🐛 Bug fixes** @@ -82,11 +84,11 @@ - Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) - Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) - PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) -- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) - Webpublisher: added check for empty context [\#3682](https://github.com/pypeclub/OpenPype/pull/3682) **🔀 Refactored code** +- General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) @@ -110,7 +112,6 @@ - Hiero: Define hiero as module [\#3717](https://github.com/pypeclub/OpenPype/pull/3717) - Deadline: better logging for DL webservice failures [\#3694](https://github.com/pypeclub/OpenPype/pull/3694) -- Photoshop: resize saved images in ExtractReview for ffmpeg [\#3676](https://github.com/pypeclub/OpenPype/pull/3676) ## [3.14.0](https://github.com/pypeclub/OpenPype/tree/3.14.0) (2022-08-18) @@ -120,21 +121,11 @@ - Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) - Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) -- Ftrack: Set task status on task creation in integrate hierarchy [\#3675](https://github.com/pypeclub/OpenPype/pull/3675) -- Maya: Disable rendering of all lights for render instances submitted through Deadline. [\#3661](https://github.com/pypeclub/OpenPype/pull/3661) **🐛 Bug fixes** - General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) -- General: Fix finding of last version [\#3656](https://github.com/pypeclub/OpenPype/pull/3656) - -**🔀 Refactored code** - -- General: Use client projects getter [\#3673](https://github.com/pypeclub/OpenPype/pull/3673) - -**Merged pull requests:** - -- Deadline: Global job pre load is not Pype 2 compatible [\#3666](https://github.com/pypeclub/OpenPype/pull/3666) +- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) diff --git a/openpype/version.py b/openpype/version.py index c042ca2625..142bd51a30 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2-nightly.3" +__version__ = "3.14.2-nightly.4" From 5fa019527b2868c010334a6c36852e32ebaa476e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 12 Sep 2022 10:26:23 +0200 Subject: [PATCH 300/513] OP-3682 - changed folder structure --- {distribution => common/openpype_common/distribution}/README.md | 0 .../openpype_common/distribution}/__init__.py | 0 .../openpype_common/distribution}/addon_distribution.py | 2 +- .../openpype_common/distribution}/file_handler.py | 0 .../distribution}/tests/test_addon_distributtion.py | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) rename {distribution => common/openpype_common/distribution}/README.md (100%) rename {distribution => common/openpype_common/distribution}/__init__.py (100%) rename {distribution => common/openpype_common/distribution}/addon_distribution.py (98%) rename {distribution => common/openpype_common/distribution}/file_handler.py (100%) rename {distribution => common/openpype_common/distribution}/tests/test_addon_distributtion.py (98%) diff --git a/distribution/README.md b/common/openpype_common/distribution/README.md similarity index 100% rename from distribution/README.md rename to common/openpype_common/distribution/README.md diff --git a/distribution/__init__.py b/common/openpype_common/distribution/__init__.py similarity index 100% rename from distribution/__init__.py rename to common/openpype_common/distribution/__init__.py diff --git a/distribution/addon_distribution.py b/common/openpype_common/distribution/addon_distribution.py similarity index 98% rename from distribution/addon_distribution.py rename to common/openpype_common/distribution/addon_distribution.py index 389b92b10b..e39ce66a0a 100644 --- a/distribution/addon_distribution.py +++ b/common/openpype_common/distribution/addon_distribution.py @@ -7,7 +7,7 @@ import requests import platform import shutil -from distribution.file_handler import RemoteFileHandler +from common.openpype_common.distribution.file_handler import RemoteFileHandler class UrlType(Enum): diff --git a/distribution/file_handler.py b/common/openpype_common/distribution/file_handler.py similarity index 100% rename from distribution/file_handler.py rename to common/openpype_common/distribution/file_handler.py diff --git a/distribution/tests/test_addon_distributtion.py b/common/openpype_common/distribution/tests/test_addon_distributtion.py similarity index 98% rename from distribution/tests/test_addon_distributtion.py rename to common/openpype_common/distribution/tests/test_addon_distributtion.py index c6ecaca3c8..7dd27fd44f 100644 --- a/distribution/tests/test_addon_distributtion.py +++ b/common/openpype_common/distribution/tests/test_addon_distributtion.py @@ -2,7 +2,7 @@ import pytest import attr import tempfile -from distribution.addon_distribution import ( +from common.openpype_common.distribution.addon_distribution import ( AddonDownloader, UrlType, OSAddonDownloader, From e76ec9e5aff5651070bcc817e9c0b60a9a980ce4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 12 Sep 2022 16:26:35 +0800 Subject: [PATCH 301/513] adding and loading mel workspace within openpype settings --- openpype/hosts/maya/resources/workspace.mel | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 openpype/hosts/maya/resources/workspace.mel diff --git a/openpype/hosts/maya/resources/workspace.mel b/openpype/hosts/maya/resources/workspace.mel deleted file mode 100644 index f7213fa4f6..0000000000 --- a/openpype/hosts/maya/resources/workspace.mel +++ /dev/null @@ -1,11 +0,0 @@ -//Maya 2018 Project Definition - -workspace -fr "shaders" "renderData/shaders"; -workspace -fr "alembicCache" "cache/alembic"; -workspace -fr "mayaAscii" ""; -workspace -fr "mayaBinary" ""; -workspace -fr "renderData" "renderData"; -workspace -fr "fileCache" "cache/nCache"; -workspace -fr "scene" ""; -workspace -fr "sourceImages" "sourceimages"; -workspace -fr "images" "renders"; From 4ba3ff21ab10e5b0c092f9580dc6444bdd61383c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Sep 2022 11:18:09 +0200 Subject: [PATCH 302/513] Tweak back more to intended logic --- .../plugins/publish/submit_maya_deadline.py | 29 ++++++++++++------- 1 file changed, 19 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 f8d0af9752..45790c40ea 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -754,7 +754,12 @@ def _format_tiles( used for assembler configuration. """ - tile = 0 + # Math used requires integers for correct output - as such + # we ensure our inputs are correct. + assert type(tiles_x) is int, "tiles_x must be an integer" + assert type(tiles_y) is int, "tiles_y must be an integer" + assert type(width) is int, "width must be an integer" + assert type(height) is int, "height must be an integer" out = {"JobInfo": {}, "PluginInfo": {}} cfg = OrderedDict() w_space = width // tiles_x @@ -762,6 +767,7 @@ def _format_tiles( cfg["TilesCropped"] = "False" + tile = 0 for tile_x in range(1, tiles_x + 1): for tile_y in reversed(range(1, tiles_y + 1)): tile_prefix = "_tile_{}x{}_{}x{}_".format( @@ -769,28 +775,31 @@ def _format_tiles( tiles_x, tiles_y ) - out_tile_index = "OutputFilename{}Tile{}".format( - str(index), tile - ) + new_filename = "{}/{}{}".format( os.path.dirname(filename), tile_prefix, os.path.basename(filename) ) - out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ - "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) - top = int(height) - (tile_y * h_space) - bottom = int(height) - ((tile_y - 1) * h_space) - 1 + top = height - (tile_y * h_space) + bottom = height - ((tile_y - 1) * h_space) - 1 left = (tile_x - 1) * w_space right = (tile_x * w_space) - 1 + # Job info + out["JobInfo"]["OutputFilename{}Tile{}".format(index, tile)] = new_filename # noqa: E501 + + # Plugin Info + out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ + "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) + out["PluginInfo"]["RegionTop{}".format(tile)] = top out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom out["PluginInfo"]["RegionLeft{}".format(tile)] = left out["PluginInfo"]["RegionRight{}".format(tile)] = right + # Tile config cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename @@ -801,5 +810,5 @@ def _format_tiles( cfg["Tile{}Height".format(tile)] = h_space tile += 1 - + return out, cfg From 41a738bd12efc48aec512a83ee36ffd9b4ddcb3a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Sep 2022 11:19:28 +0200 Subject: [PATCH 303/513] Cosmetics --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 45790c40ea..44f2b5b2b4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -760,6 +760,7 @@ def _format_tiles( assert type(tiles_y) is int, "tiles_y must be an integer" assert type(width) is int, "width must be an integer" assert type(height) is int, "height must be an integer" + out = {"JobInfo": {}, "PluginInfo": {}} cfg = OrderedDict() w_space = width // tiles_x @@ -793,7 +794,6 @@ def _format_tiles( # Plugin Info out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) - out["PluginInfo"]["RegionTop{}".format(tile)] = top out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom out["PluginInfo"]["RegionLeft{}".format(tile)] = left @@ -805,7 +805,6 @@ def _format_tiles( cfg["Tile{}FileName".format(tile)] = new_filename cfg["Tile{}X".format(tile)] = left cfg["Tile{}Y".format(tile)] = top - cfg["Tile{}Width".format(tile)] = w_space cfg["Tile{}Height".format(tile)] = h_space From c6ad515682944690d15532cd446fae2d8c93a570 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Mon, 12 Sep 2022 09:46:17 +0000 Subject: [PATCH 304/513] [Automated] Bump version --- CHANGELOG.md | 16 ++++++---------- openpype/version.py | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffb6a996b..cccfc2eded 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.14.2-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.2-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) @@ -8,7 +8,6 @@ - Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) - Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) -- Global: making collect audio plugin global [\#3679](https://github.com/pypeclub/OpenPype/pull/3679) **🚀 Enhancements** @@ -18,6 +17,7 @@ - Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) - SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) - Kitsu: Drop 'entities root' setting. [\#3739](https://github.com/pypeclub/OpenPype/pull/3739) +- git: update gitignore [\#3722](https://github.com/pypeclub/OpenPype/pull/3722) **🐛 Bug fixes** @@ -42,6 +42,8 @@ - General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) - General: Move create project folders to pipeline [\#3768](https://github.com/pypeclub/OpenPype/pull/3768) - General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) +- Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) +- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) - General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) @@ -65,12 +67,12 @@ **🚀 Enhancements** - General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) -- git: update gitignore [\#3722](https://github.com/pypeclub/OpenPype/pull/3722) - Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) - General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) - Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) - Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) - General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) +- Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) **🐛 Bug fixes** @@ -84,7 +86,7 @@ - Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) - Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) - PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) -- Webpublisher: added check for empty context [\#3682](https://github.com/pypeclub/OpenPype/pull/3682) +- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) **🔀 Refactored code** @@ -117,15 +119,9 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.0-nightly.1...3.14.0) -**🚀 Enhancements** - -- Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) -- Ftrack: Set task status on farm publishing [\#3680](https://github.com/pypeclub/OpenPype/pull/3680) - **🐛 Bug fixes** - General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) -- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) diff --git a/openpype/version.py b/openpype/version.py index 142bd51a30..c5dc4ee581 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2-nightly.4" +__version__ = "3.14.2-nightly.5" From 162370e1ad1291cbbf3eca65266c226ccd119aca Mon Sep 17 00:00:00 2001 From: OpenPype Date: Mon, 12 Sep 2022 09:56:59 +0000 Subject: [PATCH 305/513] [Automated] Release --- CHANGELOG.md | 15 +++++++++------ openpype/version.py | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cccfc2eded..46bf56f5bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.14.2-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...3.14.2) **🆕 New features** @@ -45,10 +45,11 @@ - Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) - General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) -- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) +- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) - Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) - Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) +- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) - Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) **Merged pull requests:** @@ -72,7 +73,6 @@ - Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) - Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) - General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) -- Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) **🐛 Bug fixes** @@ -91,13 +91,12 @@ **🔀 Refactored code** - General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) -- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) - AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) -- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) - AfterEffects: Define AfterEffects as module [\#3728](https://github.com/pypeclub/OpenPype/pull/3728) - General: Replace PypeLogger with Logger [\#3725](https://github.com/pypeclub/OpenPype/pull/3725) - Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) @@ -119,6 +118,10 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.0-nightly.1...3.14.0) +**🚀 Enhancements** + +- Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) + **🐛 Bug fixes** - General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) diff --git a/openpype/version.py b/openpype/version.py index c5dc4ee581..8469b1712a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2-nightly.5" +__version__ = "3.14.2" From 6e2ffc1e5ceb134f17fcedd1646f2cec0014a43a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 10:33:41 +0200 Subject: [PATCH 306/513] Remove getting project name and settings twice --- openpype/hosts/maya/api/pipeline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index acd8a55aa4..45c52cd0d5 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -66,8 +66,6 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): project_name = legacy_io.active_project() project_settings = get_project_settings(project_name) # process path mapping - project_name = legacy_io.active_project() - project_settings = get_project_settings(project_name) dirmap_processor = MayaDirmap("maya", project_name, project_settings) dirmap_processor.process_dirmap() From 37286eef2ce894d357cfaa530434f011f5ff4a59 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 11:42:38 +0200 Subject: [PATCH 307/513] propagated 'get_contexts_for_repre_docs' to load init --- openpype/pipeline/load/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/pipeline/load/__init__.py b/openpype/pipeline/load/__init__.py index bf38a0b3c8..e96f64f2a4 100644 --- a/openpype/pipeline/load/__init__.py +++ b/openpype/pipeline/load/__init__.py @@ -5,6 +5,7 @@ from .utils import ( InvalidRepresentationContext, get_repres_contexts, + get_contexts_for_repre_docs, get_subset_contexts, get_representation_context, @@ -54,6 +55,7 @@ __all__ = ( "InvalidRepresentationContext", "get_repres_contexts", + "get_contexts_for_repre_docs", "get_subset_contexts", "get_representation_context", From cacfa0999553e35af0cafb9272c83414bcfc50c5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 11:43:16 +0200 Subject: [PATCH 308/513] reduce representations to last version --- .../pipeline/workfile/new_template_loader.py | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 921cc39ba9..2cae32a04a 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -46,19 +46,6 @@ class AbstractTemplateLoader: _log = None def __init__(self, host): - # Prepare context information - project_name = legacy_io.active_project() - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - current_asset_doc = get_asset_by_name(project_name, asset_name) - task_type = ( - current_asset_doc - .get("data", {}) - .get("tasks", {}) - .get(task_name, {}) - .get("type") - ) - # Get host name if isinstance(host, HostBase): host_name = host.name @@ -1172,6 +1159,34 @@ class PlaceholderLoadMixin(object): context_filters=context_filters )) + def _reduce_last_version_repre_docs(self, representations): + """Reduce representations to last verison.""" + + mapping = {} + for repre_doc in representations: + repre_context = repre_doc["context"] + + asset_name = repre_context["asset"] + subset_name = repre_context["subset"] + version = repre_context.get("version", -1) + + if asset_name not in mapping: + mapping[asset_name] = {} + + subset_mapping = mapping[asset_name] + if subset_name not in subset_mapping: + subset_mapping[subset_name] = collections.defaultdict(list) + + version_mapping = subset_mapping[subset_name] + version_mapping[version].append(repre_doc) + + output = [] + for subset_mapping in mapping.values(): + for version_mapping in subset_mapping.values(): + last_version = tuple(sorted(version_mapping.keys()))[-1] + output.extend(version_mapping[last_version]) + return output + def populate_load_placeholder(self, placeholder, ignore_repre_ids=None): if ignore_repre_ids is None: ignore_repre_ids = set() @@ -1183,7 +1198,9 @@ class PlaceholderLoadMixin(object): placeholder_representations = self.get_representations(placeholder) filtered_representations = [] - for representation in placeholder_representations: + for representation in self._reduce_last_version_repre_docs( + placeholder_representations + ): repre_id = str(representation["_id"]) if repre_id not in ignore_repre_ids: filtered_representations.append(representation) From af895da690654573f64c542ce09ad6690fc7e817 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 11:43:51 +0200 Subject: [PATCH 309/513] few minor fixes --- .../pipeline/workfile/new_template_loader.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 2cae32a04a..65c50b9d80 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -604,6 +604,10 @@ class PlaceholderPlugin(object): return self._builder + @property + def project_name(self): + return self._builder.project_name + @property def log(self): """Dynamically created logger for the plugin.""" @@ -956,7 +960,7 @@ class PlaceholderLoadMixin(object): """Unified attribute definitions for load placeholder. Common function for placeholder plugins used for loading of - repsentations. + repsentations. Use it in 'get_placeholder_options'. Args: plugin (PlaceholderPlugin): Plugin used for loading of @@ -1125,7 +1129,7 @@ class PlaceholderLoadMixin(object): "asset": [current_asset_doc["name"]], "subset": [re.compile(placeholder.data["subset"])], "hierarchy": [re.compile(placeholder.data["hierarchy"])], - "representations": [placeholder.data["representation"]], + "representation": [placeholder.data["representation"]], "family": [placeholder.data["family"]] } @@ -1188,6 +1192,23 @@ class PlaceholderLoadMixin(object): return output def populate_load_placeholder(self, placeholder, ignore_repre_ids=None): + """Load placeholder is goind to load matching representations. + + Note: + Ignore repre ids is to avoid loading the same representation again + on load. But the representation can be loaded with different loader + and there could be published new version of matching subset for the + representation. We should maybe expect containers. + + Also import loaders don't have containers at all... + + Args: + placeholder (PlaceholderItem): Placeholder item with information + about requested representations. + ignore_repre_ids (Iterable[Union[str, ObjectId]]): Representation + ids that should be skipped. + """ + if ignore_repre_ids is None: ignore_repre_ids = set() @@ -1215,7 +1236,7 @@ class PlaceholderLoadMixin(object): self.project_name, filtered_representations ) loaders_by_name = self.builder.get_loaders_by_name() - for repre_load_context in repre_load_contexts: + for repre_load_context in repre_load_contexts.values(): representation = repre_load_context["representation"] repre_context = representation["context"] self.log.info( From 09c73beec90df7b493e83e481fc97313a4d7a850 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 11:44:04 +0200 Subject: [PATCH 310/513] added option to have callback before load --- openpype/pipeline/workfile/new_template_loader.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 65c50b9d80..07ff69d1f5 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -1163,6 +1163,11 @@ class PlaceholderLoadMixin(object): context_filters=context_filters )) + def _before_repre_load(self, placeholder, representation): + """Can be overriden. Is called before representation is loaded.""" + + pass + def _reduce_last_version_repre_docs(self, representations): """Reduce representations to last verison.""" @@ -1239,6 +1244,9 @@ class PlaceholderLoadMixin(object): for repre_load_context in repre_load_contexts.values(): representation = repre_load_context["representation"] repre_context = representation["context"] + self._before_repre_load( + placeholder, representation + ) self.log.info( "Loading {} from {} with loader {}\n" "Loader arguments used : {}".format( From c702d117172c3b7561eedd5cdb70ada3b5a399cc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 11:44:37 +0200 Subject: [PATCH 311/513] moved cleanup logic to plugin responsibility instead of placeholder's --- .../maya/api/workfile_template_builder.py | 48 ++++++++++--------- .../pipeline/workfile/new_template_loader.py | 5 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 14f1f284fd..42736badf2 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -216,6 +216,31 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): def get_placeholder_options(self, options=None): return self.get_load_plugin_options(self, options) + def cleanup_placeholder(self, placeholder): + """Hide placeholder, parent them to root + add them to placeholder set and register placeholder's parent + to keep placeholder info available for future use + """ + + node = placeholder._scene_identifier + node_parent = placeholder.data["parent"] + if node_parent: + cmds.setAttr(node + ".parent", node_parent, type="string") + + if cmds.getAttr(node + ".index") < 0: + cmds.setAttr(node + ".index", placeholder.data["index"]) + + holding_sets = cmds.listSets(object=node) + if holding_sets: + for set in holding_sets: + cmds.sets(node, remove=set) + + if cmds.listRelatives(node, p=True): + node = cmds.parent(node, world=True)[0] + cmds.sets(node, addElement=PLACEHOLDER_SET) + cmds.hide(node) + cmds.setAttr(node + ".hiddenInOutliner", True) + class LoadPlaceholderItem(PlaceholderItem): """Concrete implementation of PlaceholderItem for Maya load plugin.""" @@ -269,29 +294,6 @@ class LoadPlaceholderItem(PlaceholderItem): for holding_set in holding_sets: cmds.sets(roots, forceElement=holding_set) - def clean(self): - """Hide placeholder, parent them to root - add them to placeholder set and register placeholder's parent - to keep placeholder info available for future use - """ - - node = self._scene_identifier - if self.data['parent']: - cmds.setAttr(node + '.parent', self.data['parent'], type='string') - if cmds.getAttr(node + '.index') < 0: - cmds.setAttr(node + '.index', self.data['index']) - - holding_sets = cmds.listSets(object=node) - if holding_sets: - for set in holding_sets: - cmds.sets(node, remove=set) - - if cmds.listRelatives(node, p=True): - node = cmds.parent(node, world=True)[0] - cmds.sets(node, addElement=PLACEHOLDER_SET) - cmds.hide(node) - cmds.setAttr(node + ".hiddenInOutliner", True) - def get_errors(self): if not self._failed_representations: return [] diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/new_template_loader.py index 07ff69d1f5..3f81ce0114 100644 --- a/openpype/pipeline/workfile/new_template_loader.py +++ b/openpype/pipeline/workfile/new_template_loader.py @@ -1268,4 +1268,7 @@ class PlaceholderLoadMixin(object): else: placeholder.load_succeed(container) - placeholder.clean() + self.cleanup_placeholder(placeholder) + + def cleanup_placeholder(self, placeholder): + pass From f36b0aa2c6026d093ffc7421a52eb9c59264c740 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 11:48:41 +0200 Subject: [PATCH 312/513] initial commit of nuke workfile builder --- openpype/hosts/nuke/api/__init__.py | 4 + openpype/hosts/nuke/api/pipeline.py | 6 + .../nuke/api/workfile_template_builder.py | 607 ++++++++++++++++++ 3 files changed, 617 insertions(+) create mode 100644 openpype/hosts/nuke/api/workfile_template_builder.py diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index 962f31c177..c65058874b 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -21,6 +21,8 @@ from .pipeline import ( containerise, parse_container, update_container, + + get_workfile_build_placeholder_plugins, ) from .lib import ( maintained_selection, @@ -55,6 +57,8 @@ __all__ = ( "parse_container", "update_container", + "get_workfile_build_placeholder_plugins", + "maintained_selection", "reset_selection", "get_view_process_node", diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index bac42128cc..d4edd24cf6 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -141,6 +141,12 @@ def _show_workfiles(): host_tools.show_workfiles(parent=None, on_top=False) +def get_workfile_build_placeholder_plugins(): + return [ + NukePlaceholderLoadPlugin + ] + + def _install_menu(): # uninstall original avalon menu main_window = get_main_window() diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py new file mode 100644 index 0000000000..71ea5c95a5 --- /dev/null +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -0,0 +1,607 @@ +import json +import collections + +import nuke + +from openpype.pipeline import registered_host +from openpype.pipeline.workfile.build_template_exceptions import ( + TemplateAlreadyImported +) +from openpype.pipeline.workfile.new_template_loader import ( + AbstractTemplateLoader, + PlaceholderPlugin, + PlaceholderItem, + PlaceholderLoadMixin, +) +from openpype.tools.workfile_template_build import ( + WorkfileBuildPlaceholderDialog, +) + +from .lib import ( + find_free_space_to_paste_nodes, + get_extreme_positions, + get_group_io_nodes, + imprint, + refresh_node, + refresh_nodes, + reset_selection, + get_names_from_nodes, + get_nodes_by_names, + select_nodes, + duplicate_node, + node_tempfile, +) + +PLACEHOLDER_SET = "PLACEHOLDERS_SET" + + +class NukeTemplateLoader(AbstractTemplateLoader): + """Concrete implementation of AbstractTemplateLoader for maya""" + + def import_template(self, path): + """Import template into current scene. + Block if a template is already loaded. + + Args: + path (str): A path to current template (usually given by + get_template_path implementation) + + Returns: + bool: Wether the template was succesfully imported or not + """ + + # TODO check if the template is already imported + + nuke.nodePaste(path) + reset_selection() + + return True + + +class NukePlaceholderPlugin(PlaceholderPlugin): + noce_color = 4278190335 + + def _collect_scene_placeholders(self): + # Cache placeholder data to shared data + placeholder_nodes = self.builder.get_shared_populate_data( + "placeholder_nodes" + ) + if placeholder_nodes is None: + placeholder_nodes = {} + all_groups = collections.deque() + all_groups.append(nuke.thisGroup()) + while all_groups: + group = all_groups.popleft() + for node in group.nodes(): + if isinstance(node, nuke.Group): + all_groups.append(node) + + node_knobs = node.knobs() + if ( + "builder_type" not in node_knobs + or "is_placeholder" not in node_knobs + or not node.knob("is_placeholder").value() + ): + continue + + if "empty" in node_knobs and node.knob("empty").value(): + continue + + placeholder_nodes[node.fullName()] = node + + self.builder.set_shared_populate_data( + "placeholder_nodes", placeholder_nodes + ) + return placeholder_nodes + + def create_placeholder(self, placeholder_data): + placeholder_data["plugin_identifier"] = self.identifier + + placeholder = nuke.nodes.NoOp() + placeholder.setName("PLACEHOLDER") + placeholder.knob("tile_color").setValue(self.node_color) + + imprint(placeholder, placeholder_data) + imprint(placeholder, {"is_placeholder": True}) + placeholder.knob("is_placeholder").setVisible(False) + + def update_placeholder(self, placeholder_item, placeholder_data): + node = nuke.toNode(placeholder_item.scene_identifier) + imprint(node, placeholder_data) + + def _parse_placeholder_node_data(self, node): + placeholder_data = {} + for key in self.get_placeholder_keys(): + knob = node.knob(key) + value = None + if knob is not None: + value = knob.getValue() + placeholder_data[key] = value + return placeholder_data + + +class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): + identifier = "nuke.load" + label = "Nuke load" + + def _parse_placeholder_node_data(self, node): + placeholder_data = super( + NukePlaceholderLoadPlugin, self + )._parse_placeholder_node_data(node) + + node_knobs = node.knobs() + nb_children = 0 + if "nb_children" in node_knobs: + nb_children = int(node_knobs["nb_children"].getValue()) + placeholder_data["nb_children"] = nb_children + + siblings = [] + if "siblings" in node_knobs: + siblings = node_knobs["siblings"].values() + placeholder_data["siblings"] = siblings + + node_full_name = node.fullName() + placeholder_data["group_name"] = node_full_name.rpartition(".")[0] + placeholder_data["last_loaded"] = [] + placeholder_data["delete"] = False + return placeholder_data + + def _get_loaded_repre_ids(self): + loaded_representation_ids = self.builder.get_shared_populate_data( + "loaded_representation_ids" + ) + if loaded_representation_ids is None: + loaded_representation_ids = set() + for node in nuke.allNodes(): + if "repre_id" in node.knobs(): + loaded_representation_ids.add( + node.knob("repre_id").getValue() + ) + + self.builder.set_shared_populate_data( + "loaded_representation_ids", loaded_representation_ids + ) + return loaded_representation_ids + + def _before_repre_load(self, placeholder, representation): + placeholder.data["nodes_init"] = nuke.allNodes() + placeholder.data["last_repre_id"] = str(representation["_id"]) + + def collect_placeholders(self): + output = [] + scene_placeholders = self._collect_scene_placeholders() + for node_name, node in scene_placeholders.items(): + plugin_identifier_knob = node.knob("plugin_identifier") + if ( + plugin_identifier_knob is None + or plugin_identifier_knob.getValue() != self.identifier + ): + continue + + placeholder_data = self._parse_placeholder_node_data(node) + # TODO do data validations and maybe updgrades if are invalid + output.append( + NukeLoadPlaceholderItem(node_name, placeholder_data, self) + ) + + return output + + def populate_placeholder(self, placeholder): + self.populate_load_placeholder(placeholder) + + def update_template_placeholder(self, placeholder): + repre_ids = self._get_loaded_repre_ids() + self.populate_load_placeholder(placeholder, repre_ids) + + def get_placeholder_options(self, options=None): + return self.get_load_plugin_options(options) + + def cleanup_placeholder(self, placeholder): + # deselect all selected nodes + placeholder_node = nuke.toNode(placeholder.scene_identifier) + + # getting the latest nodes added + # TODO get from shared populate data! + nodes_init = placeholder.data["nodes_init"] + nodes_loaded = list(set(nuke.allNodes()) - set(nodes_init)) + self.log.debug("Loaded nodes: {}".format(nodes_loaded)) + if not nodes_loaded: + return + + placeholder.data["delete"] = True + + nodes_loaded = self._move_to_placeholder_group( + placeholder, nodes_loaded + ) + placeholder.data["last_loaded"] = nodes_loaded + refresh_nodes(nodes_loaded) + + # positioning of the loaded nodes + min_x, min_y, _, _ = get_extreme_positions(nodes_loaded) + for node in nodes_loaded: + xpos = (node.xpos() - min_x) + placeholder_node.xpos() + ypos = (node.ypos() - min_y) + placeholder_node.ypos() + node.setXYpos(xpos, ypos) + refresh_nodes(nodes_loaded) + + # fix the problem of z_order for backdrops + self._fix_z_order(placeholder) + self._imprint_siblings(placeholder) + + if placeholder.data["nb_children"] == 0: + # save initial nodes postions and dimensions, update them + # and set inputs and outputs of loaded nodes + + self._imprint_inits() + self._update_nodes(placeholder, nuke.allNodes(), nodes_loaded) + self._set_loaded_connections(placeholder) + + elif placeholder.data["siblings"]: + # create copies of placeholder siblings for the new loaded nodes, + # set their inputs and outpus and update all nodes positions and + # dimensions and siblings names + + siblings = get_nodes_by_names(placeholder.data["siblings"]) + refresh_nodes(siblings) + copies = self._create_sib_copies(placeholder) + new_nodes = list(copies.values()) # copies nodes + self._update_nodes(new_nodes, nodes_loaded) + placeholder_node.removeKnob(placeholder_node.knob("siblings")) + new_nodes_name = get_names_from_nodes(new_nodes) + imprint(placeholder_node, {"siblings": new_nodes_name}) + self._set_copies_connections(placeholder, copies) + + self._update_nodes( + nuke.allNodes(), + new_nodes + nodes_loaded, + 20 + ) + + new_siblings = get_names_from_nodes(new_nodes) + placeholder.data["siblings"] = new_siblings + + else: + # if the placeholder doesn't have siblings, the loaded + # nodes will be placed in a free space + + xpointer, ypointer = find_free_space_to_paste_nodes( + nodes_loaded, direction="bottom", offset=200 + ) + node = nuke.createNode("NoOp") + reset_selection() + nuke.delete(node) + for node in nodes_loaded: + xpos = (node.xpos() - min_x) + xpointer + ypos = (node.ypos() - min_y) + ypointer + node.setXYpos(xpos, ypos) + + placeholder.data["nb_children"] += 1 + reset_selection() + # go back to root group + nuke.root().begin() + + def _move_to_placeholder_group(self, placeholder, nodes_loaded): + """ + opening the placeholder's group and copying loaded nodes in it. + + Returns : + nodes_loaded (list): the new list of pasted nodes + """ + + groups_name = placeholder.data["group_name"] + reset_selection() + select_nodes(nodes_loaded) + if groups_name: + with node_tempfile() as filepath: + nuke.nodeCopy(filepath) + for node in nuke.selectedNodes(): + nuke.delete(node) + group = nuke.toNode(groups_name) + group.begin() + nuke.nodePaste(filepath) + nodes_loaded = nuke.selectedNodes() + return nodes_loaded + + def _fix_z_order(self, placeholder): + """Fix the problem of z_order when a backdrop is loaded.""" + + nodes_loaded = placeholder.data["last_loaded"] + loaded_backdrops = [] + bd_orders = set() + for node in nodes_loaded: + if isinstance(node, nuke.BackdropNode): + loaded_backdrops.append(node) + bd_orders.add(node.knob("z_order").getValue()) + + if not bd_orders: + return + + sib_orders = set() + for node_name in placeholder.data["siblings"]: + node = nuke.toNode(node_name) + if isinstance(node, nuke.BackdropNode): + sib_orders.add(node.knob("z_order").getValue()) + + if not sib_orders: + return + + min_order = min(bd_orders) + max_order = max(sib_orders) + for backdrop_node in loaded_backdrops: + z_order = backdrop_node.knob("z_order").getValue() + backdrop_node.knob("z_order").setValue( + z_order + max_order - min_order + 1) + + def _imprint_siblings(self, placeholder): + """ + - add siblings names to placeholder attributes (nodes loaded with it) + - add Id to the attributes of all the other nodes + """ + + loaded_nodes = placeholder.data["last_loaded"] + loaded_nodes_set = set(loaded_nodes) + data = {"repre_id": str(placeholder.data["last_repre_id"])} + + for node in loaded_nodes: + node_knobs = node.knobs() + if "builder_type" not in node_knobs: + # save the id of representation for all imported nodes + imprint(node, data) + node.knob("repre_id").setVisible(False) + refresh_node(node) + continue + + if ( + "is_placeholder" not in node_knobs + or ( + "is_placeholder" in node_knobs + and node.knob("is_placeholder").value() + ) + ): + siblings = list(loaded_nodes_set - {node}) + siblings_name = get_names_from_nodes(siblings) + siblings = {"siblings": siblings_name} + imprint(node, siblings) + + def _imprint_inits(self): + """Add initial positions and dimensions to the attributes""" + + for node in nuke.allNodes(): + refresh_node(node) + imprint(node, {"x_init": node.xpos(), "y_init": node.ypos()}) + node.knob("x_init").setVisible(False) + node.knob("y_init").setVisible(False) + width = node.screenWidth() + height = node.screenHeight() + if "bdwidth" in node.knobs(): + imprint(node, {"w_init": width, "h_init": height}) + node.knob("w_init").setVisible(False) + node.knob("h_init").setVisible(False) + refresh_node(node) + + def _update_nodes( + self, placeholder, nodes, considered_nodes, offset_y=None + ): + """Adjust backdrop nodes dimensions and positions. + + Considering some nodes sizes. + + Args: + nodes (list): list of nodes to update + considered_nodes (list): list of nodes to consider while updating + positions and dimensions + offset (int): distance between copies + """ + + placeholder_node = nuke.toNode(placeholder.scene_identifier) + + min_x, min_y, max_x, max_y = get_extreme_positions(considered_nodes) + + diff_x = diff_y = 0 + contained_nodes = [] # for backdrops + + if offset_y is None: + width_ph = placeholder_node.screenWidth() + height_ph = placeholder_node.screenHeight() + diff_y = max_y - min_y - height_ph + diff_x = max_x - min_x - width_ph + contained_nodes = [placeholder_node] + min_x = placeholder_node.xpos() + min_y = placeholder_node.ypos() + else: + siblings = get_nodes_by_names(placeholder.data["siblings"]) + minX, _, maxX, _ = get_extreme_positions(siblings) + diff_y = max_y - min_y + 20 + diff_x = abs(max_x - min_x - maxX + minX) + contained_nodes = considered_nodes + + if diff_y <= 0 and diff_x <= 0: + return + + for node in nodes: + refresh_node(node) + + if ( + node == placeholder_node + or node in considered_nodes + ): + continue + + if ( + not isinstance(node, nuke.BackdropNode) + or ( + isinstance(node, nuke.BackdropNode) + and not set(contained_nodes) <= set(node.getNodes()) + ) + ): + if offset_y is None and node.xpos() >= min_x: + node.setXpos(node.xpos() + diff_x) + + if node.ypos() >= min_y: + node.setYpos(node.ypos() + diff_y) + + else: + width = node.screenWidth() + height = node.screenHeight() + node.knob("bdwidth").setValue(width + diff_x) + node.knob("bdheight").setValue(height + diff_y) + + refresh_node(node) + + def _set_loaded_connections(self, placeholder): + """ + set inputs and outputs of loaded nodes""" + + placeholder_node = nuke.toNode(placeholder.scene_identifier) + input_node, output_node = get_group_io_nodes( + placeholder.data["last_loaded"] + ) + for node in placeholder_node.dependent(): + for idx in range(node.inputs()): + if node.input(idx) == placeholder_node: + node.setInput(idx, output_node) + + for node in placeholder_node.dependencies(): + for idx in range(placeholder_node.inputs()): + if placeholder_node.input(idx) == node: + input_node.setInput(0, node) + + def _create_sib_copies(self, placeholder): + """ creating copies of the palce_holder siblings (the ones who were + loaded with it) for the new nodes added + + Returns : + copies (dict) : with copied nodes names and their copies + """ + + copies = {} + siblings = get_nodes_by_names(placeholder.data["siblings"]) + for node in siblings: + new_node = duplicate_node(node) + + x_init = int(new_node.knob("x_init").getValue()) + y_init = int(new_node.knob("y_init").getValue()) + new_node.setXYpos(x_init, y_init) + if isinstance(new_node, nuke.BackdropNode): + w_init = new_node.knob("w_init").getValue() + h_init = new_node.knob("h_init").getValue() + new_node.knob("bdwidth").setValue(w_init) + new_node.knob("bdheight").setValue(h_init) + refresh_node(node) + + if "repre_id" in node.knobs().keys(): + node.removeKnob(node.knob("repre_id")) + copies[node.name()] = new_node + return copies + + def _set_copies_connections(self, placeholder, copies): + """Set inputs and outputs of the copies. + + Args: + copies (dict): Copied nodes by their names. + """ + + last_input, last_output = get_group_io_nodes( + placeholder.data["last_loaded"] + ) + siblings = get_nodes_by_names(placeholder.data["siblings"]) + siblings_input, siblings_output = get_group_io_nodes(siblings) + copy_input = copies[siblings_input.name()] + copy_output = copies[siblings_output.name()] + + for node_init in siblings: + if node_init == siblings_output: + continue + + node_copy = copies[node_init.name()] + for node in node_init.dependent(): + for idx in range(node.inputs()): + if node.input(idx) != node_init: + continue + + if node in siblings: + copies[node.name()].setInput(idx, node_copy) + else: + last_input.setInput(0, node_copy) + + for node in node_init.dependencies(): + for idx in range(node_init.inputs()): + if node_init.input(idx) != node: + continue + + if node_init == siblings_input: + copy_input.setInput(idx, node) + elif node in siblings: + node_copy.setInput(idx, copies[node.name()]) + else: + node_copy.setInput(idx, last_output) + + siblings_input.setInput(0, copy_output) + + +class NukeLoadPlaceholderItem(PlaceholderItem): + """Concrete implementation of PlaceholderItem for Maya load plugin.""" + + def __init__(self, *args, **kwargs): + super(NukeLoadPlaceholderItem, self).__init__(*args, **kwargs) + self._failed_representations = [] + + def get_errors(self): + if not self._failed_representations: + return [] + message = ( + "Failed to load {} representations using Loader {}" + ).format( + len(self._failed_representations), + self.data["loader"] + ) + return [message] + + def load_failed(self, representation): + self._failed_representations.append(representation) + + def load_succeed(self, container): + pass + + +def build_workfile_template(*args): + builder = NukeTemplateLoader(registered_host()) + builder.build_template() + + +def update_workfile_template(*args): + builder = NukeTemplateLoader(registered_host()) + builder.rebuild_template() + + +def create_placeholder(*args): + host = registered_host() + builder = NukeTemplateLoader(host) + window = WorkfileBuildPlaceholderDialog(host, builder) + window.exec_() + + +def update_placeholder(*args): + host = registered_host() + builder = NukeTemplateLoader(host) + placeholder_items_by_id = { + placeholder_item.scene_identifier: placeholder_item + for placeholder_item in builder.get_placeholders() + } + placeholder_items = [] + for node in nuke.selectedNodes(): + node_name = node.fullName() + if node_name in placeholder_items_by_id: + placeholder_items.append(placeholder_items_by_id[node_name]) + + # TODO show UI at least + if len(placeholder_items) == 0: + raise ValueError("No node selected") + + if len(placeholder_items) > 1: + raise ValueError("Too many selected nodes") + + placeholder_item = placeholder_items[0] + window = WorkfileBuildPlaceholderDialog(host, builder) + window.set_update_mode(placeholder_item) + window.exec_() From 34dc71936b626ed6b15de64003d086de3d3625a3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 11:51:22 +0200 Subject: [PATCH 313/513] use new workfile building system in nuke --- openpype/hosts/nuke/api/pipeline.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index d4edd24cf6..c6ccfaeb3a 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -22,10 +22,6 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.workfile import BuildWorkfile -from openpype.pipeline.workfile.build_template import ( - build_workfile_template, - update_workfile_template -) from openpype.tools.utils import host_tools from .command import viewer_update_and_undo_stop @@ -40,8 +36,12 @@ from .lib import ( set_avalon_knob_data, read_avalon_data, ) -from .lib_template_builder import ( - create_placeholder, update_placeholder +from .workfile_template_builder import ( + NukePlaceholderLoadPlugin, + build_workfile_template, + update_workfile_template, + create_placeholder, + update_placeholder, ) log = Logger.get_logger(__name__) From 703169e1706e50ec02d6a05959bf2e8504ebac5c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 12:00:34 +0200 Subject: [PATCH 314/513] removed unused imports --- openpype/hosts/nuke/api/workfile_template_builder.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index 71ea5c95a5..d018b9b598 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -1,12 +1,8 @@ -import json import collections import nuke from openpype.pipeline import registered_host -from openpype.pipeline.workfile.build_template_exceptions import ( - TemplateAlreadyImported -) from openpype.pipeline.workfile.new_template_loader import ( AbstractTemplateLoader, PlaceholderPlugin, From f6792d2e420fc75d5a3c4aee489c40a48be56d6c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 13 Sep 2022 18:04:45 +0800 Subject: [PATCH 315/513] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 27 ++++------ openpype/pipeline/workfile/lock_workfile.py | 14 ++---- openpype/tools/workfiles/files_widget.py | 14 ++---- openpype/tools/workfiles/lock_dialog.py | 55 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 36 deletions(-) create mode 100644 openpype/tools/workfiles/lock_dialog.py diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index b34a216c13..5c7a7abf4d 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -480,6 +480,7 @@ def on_before_save(): def check_lock_on_current_file(): + """Check if there is a user opening the file""" if not handle_workfile_locks(): return @@ -492,23 +493,15 @@ def check_lock_on_current_file(): create_workfile_lock(filepath) return - username = get_user_from_lock(filepath) - reminder = cmds.window(title="Reminder", width=400, height=30) - cmds.columnLayout(adjustableColumn=True) - cmds.separator() - cmds.columnLayout(adjustableColumn=True) - comment = " %s is working the same workfile!" % username - cmds.text(comment, align='center') - cmds.text(vis=False) - cmds.rowColumnLayout(numberOfColumns=3, - columnWidth=[(1, 200), (2, 100), (3, 100)], - columnSpacing=[(3, 10)]) - cmds.separator(vis=False) - cancel_command = "cmds.file(new=True);cmds.deleteUI('%s')" % reminder - ignore_command = "cmds.deleteUI('%s')" % reminder - cmds.button(label='Cancel', command=cancel_command) - cmds.button(label="Ignore", command=ignore_command) - cmds.showWindow(reminder) + # add lockfile dialog + from Qt import QtWidgets + from openpype.tools.workfiles.lock_dialog import WorkfileLockDialog + + top_level_widgets = {w.objectName(): w for w in + QtWidgets.QApplication.topLevelWidgets()} + parent = top_level_widgets.get("MayaWindow", None) + workfile_dialog = WorkfileLockDialog(filepath, parent=parent) + workfile_dialog.show() def on_before_close(): diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index 7c8c4a8066..fbec44247a 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -26,6 +26,11 @@ def is_workfile_locked(filepath): return True +def get_workfile_lock_data(filepath): + lock_filepath = _get_lock_file(filepath) + return _read_lock_file(lock_filepath) + + def is_workfile_locked_for_current_process(filepath): if not is_workfile_locked(filepath): return False @@ -49,15 +54,6 @@ def create_workfile_lock(filepath): json.dump(info, stream) -def get_user_from_lock(filepath): - lock_filepath = _get_lock_file(filepath) - if not os.path.exists(lock_filepath): - return - data = _read_lock_file(lock_filepath) - username = data["username"] - return username - - def remove_workfile_lock(filepath): if is_workfile_locked_for_current_process(filepath): delete_workfile_lock(filepath) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 5eab3af144..c1c647478d 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -10,7 +10,6 @@ from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id from openpype.pipeline.workfile.lock_workfile import ( is_workfile_locked, - get_user_from_lock, is_workfile_lock_enabled, is_workfile_locked_for_current_process ) @@ -20,6 +19,7 @@ from openpype.lib import ( emit_event, create_workdir_extra_folders, ) +from openpype.tools.workfiles.lock_dialog import WorkfileLockDialog from openpype.pipeline import ( registered_host, legacy_io, @@ -30,6 +30,7 @@ from openpype.pipeline.context_tools import ( change_current_context ) from openpype.pipeline.workfile import get_workfile_template_key +from openpype.tools.workfiles.lock_dialog import WorkfileLockDialog from .model import ( WorkAreaFilesModel, @@ -468,15 +469,8 @@ class FilesWidget(QtWidgets.QWidget): def open_file(self, filepath): host = self.host if self._is_workfile_locked(filepath): - username = get_user_from_lock(filepath) - popup_dialog = QtWidgets.QMessageBox(parent=self) - popup_dialog.setWindowTitle("Warning") - popup_dialog.setText(username + " is using the file") - popup_dialog.setStandardButtons(popup_dialog.Ok) - - result = popup_dialog.exec_() - if result == popup_dialog.Ok: - return False + # add lockfile dialog + WorkfileLockDialog(filepath, parent=self) if isinstance(host, IWorkfileHost): has_unsaved_changes = host.workfile_has_unsaved_changes() diff --git a/openpype/tools/workfiles/lock_dialog.py b/openpype/tools/workfiles/lock_dialog.py new file mode 100644 index 0000000000..6c0ad6e850 --- /dev/null +++ b/openpype/tools/workfiles/lock_dialog.py @@ -0,0 +1,55 @@ +from Qt import QtWidgets, QtCore, QtGui +from openpype.style import load_stylesheet, get_app_icon_path + +from openpype.pipeline.workfile.lock_workfile import get_workfile_lock_data + + +class WorkfileLockDialog(QtWidgets.QDialog): + def __init__(self, workfile_path, parent=None): + super(WorkfileLockDialog, self).__init__(parent) + self.setWindowTitle("Warning") + icon = QtGui.QIcon(get_app_icon_path()) + self.setWindowIcon(icon) + + data = get_workfile_lock_data(workfile_path) + + message = "{} on {} machine is working on the same workfile.".format( + data["username"], + data["hostname"] + ) + + msg_label = QtWidgets.QLabel(message, self) + + btns_widget = QtWidgets.QWidget(self) + + cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget) + ignore_btn = QtWidgets.QPushButton("Ignore lock", btns_widget) + + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + btns_layout.setContentsMargins(0, 0, 0, 0) + btns_layout.setSpacing(10) + btns_layout.addStretch(1) + btns_layout.addWidget(cancel_btn, 0) + btns_layout.addWidget(ignore_btn, 0) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(15, 15, 15, 15) + main_layout.addWidget(msg_label, 1, QtCore.Qt.AlignCenter), + main_layout.addSpacing(10) + main_layout.addWidget(btns_widget, 0) + + cancel_btn.clicked.connect(self._on_cancel_click) + ignore_btn.clicked.connect(self._on_ignore_click) + + def showEvent(self, event): + super(WorkfileLockDialog, self).showEvent(event) + + self.setStyleSheet(load_stylesheet()) + + def _on_ignore_click(self): + # Result is '1' + self.accept() + + def _on_cancel_click(self): + # Result is '0' + self.reject() From 3c949aaec3a9cffb6830a100e5b9dcec3f18b1aa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 13 Sep 2022 18:06:35 +0800 Subject: [PATCH 316/513] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 1 - openpype/tools/workfiles/files_widget.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 5c7a7abf4d..87f34e1c05 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -33,7 +33,6 @@ from openpype.pipeline import ( from openpype.pipeline.load import any_outdated_containers from openpype.pipeline.workfile.lock_workfile import ( create_workfile_lock, - get_user_from_lock, remove_workfile_lock, is_workfile_locked, is_workfile_lock_enabled diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index c1c647478d..b59a7eccc5 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -30,7 +30,7 @@ from openpype.pipeline.context_tools import ( change_current_context ) from openpype.pipeline.workfile import get_workfile_template_key -from openpype.tools.workfiles.lock_dialog import WorkfileLockDialog + from .model import ( WorkAreaFilesModel, From 085ec8989092af6b0a478ab2d414975285476e19 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 12:17:14 +0200 Subject: [PATCH 317/513] renamed 'new_template_loader' to 'workfile_template_builder' --- .../{new_template_loader.py => workfile_template_builder.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/pipeline/workfile/{new_template_loader.py => workfile_template_builder.py} (100%) diff --git a/openpype/pipeline/workfile/new_template_loader.py b/openpype/pipeline/workfile/workfile_template_builder.py similarity index 100% rename from openpype/pipeline/workfile/new_template_loader.py rename to openpype/pipeline/workfile/workfile_template_builder.py From 518c3f75cabdce037c42f5132b50e623bc9c88de Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 12:18:09 +0200 Subject: [PATCH 318/513] changed AbstractTemplateLoader to AbstractTemplateBuilder --- .../hosts/maya/api/workfile_template_builder.py | 16 ++++++++-------- .../hosts/nuke/api/workfile_template_builder.py | 16 ++++++++-------- .../workfile/workfile_template_builder.py | 10 ++++++++-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 42736badf2..b947a51aaa 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -6,8 +6,8 @@ from openpype.pipeline import registered_host from openpype.pipeline.workfile.build_template_exceptions import ( TemplateAlreadyImported ) -from openpype.pipeline.workfile.new_template_loader import ( - AbstractTemplateLoader, +from openpype.pipeline.workfile.workfile_template_builder import ( + AbstractTemplateBuilder, PlaceholderPlugin, PlaceholderItem, PlaceholderLoadMixin, @@ -21,8 +21,8 @@ from .lib import read, imprint PLACEHOLDER_SET = "PLACEHOLDERS_SET" -class MayaTemplateLoader(AbstractTemplateLoader): - """Concrete implementation of AbstractTemplateLoader for maya""" +class MayaTemplateBuilder(AbstractTemplateBuilder): + """Concrete implementation of AbstractTemplateBuilder for maya""" def import_template(self, path): """Import template into current scene. @@ -313,25 +313,25 @@ class LoadPlaceholderItem(PlaceholderItem): def build_workfile_template(*args): - builder = MayaTemplateLoader(registered_host()) + builder = MayaTemplateBuilder(registered_host()) builder.build_template() def update_workfile_template(*args): - builder = MayaTemplateLoader(registered_host()) + builder = MayaTemplateBuilder(registered_host()) builder.rebuild_template() def create_placeholder(*args): host = registered_host() - builder = MayaTemplateLoader(host) + builder = MayaTemplateBuilder(host) window = WorkfileBuildPlaceholderDialog(host, builder) window.exec_() def update_placeholder(*args): host = registered_host() - builder = MayaTemplateLoader(host) + builder = MayaTemplateBuilder(host) placeholder_items_by_id = { placeholder_item.scene_identifier: placeholder_item for placeholder_item in builder.get_placeholders() diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index d018b9b598..f4dfac1e32 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -3,8 +3,8 @@ import collections import nuke from openpype.pipeline import registered_host -from openpype.pipeline.workfile.new_template_loader import ( - AbstractTemplateLoader, +from openpype.pipeline.workfile.workfile_template_builder import ( + AbstractTemplateBuilder, PlaceholderPlugin, PlaceholderItem, PlaceholderLoadMixin, @@ -31,8 +31,8 @@ from .lib import ( PLACEHOLDER_SET = "PLACEHOLDERS_SET" -class NukeTemplateLoader(AbstractTemplateLoader): - """Concrete implementation of AbstractTemplateLoader for maya""" +class NukeTemplateBuilder(AbstractTemplateBuilder): + """Concrete implementation of AbstractTemplateBuilder for maya""" def import_template(self, path): """Import template into current scene. @@ -561,25 +561,25 @@ class NukeLoadPlaceholderItem(PlaceholderItem): def build_workfile_template(*args): - builder = NukeTemplateLoader(registered_host()) + builder = NukeTemplateBuilder(registered_host()) builder.build_template() def update_workfile_template(*args): - builder = NukeTemplateLoader(registered_host()) + builder = NukeTemplateBuilder(registered_host()) builder.rebuild_template() def create_placeholder(*args): host = registered_host() - builder = NukeTemplateLoader(host) + builder = NukeTemplateBuilder(host) window = WorkfileBuildPlaceholderDialog(host, builder) window.exec_() def update_placeholder(*args): host = registered_host() - builder = NukeTemplateLoader(host) + builder = NukeTemplateBuilder(host) placeholder_items_by_id = { placeholder_item.scene_identifier: placeholder_item for placeholder_item in builder.get_placeholders() diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 3f81ce0114..4c6f3939e5 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -36,8 +36,14 @@ from .build_template_exceptions import ( @six.add_metaclass(ABCMeta) -class AbstractTemplateLoader: - """Abstraction of Template Loader. +class AbstractTemplateBuilder(object): + """Abstraction of Template Builder. + + Builder cares about context, shared data, cache, discovery of plugins + and trigger logic. Provides public api for host workfile build systen. + + Rest of logic is based on plugins that care about collection and creation + of placeholder items. Args: host (Union[HostBase, ModuleType]): Implementation of host. From 30780efd487ee22a4214bdaf6f09ebb48e66e004 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 12:18:38 +0200 Subject: [PATCH 319/513] renamed method 'update_template_placeholder' to 'repopulate_placeholder' --- openpype/hosts/maya/api/workfile_template_builder.py | 2 +- openpype/hosts/nuke/api/workfile_template_builder.py | 2 +- openpype/pipeline/workfile/workfile_template_builder.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index b947a51aaa..5fd2113bdb 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -209,7 +209,7 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): def populate_placeholder(self, placeholder): self.populate_load_placeholder(placeholder) - def update_template_placeholder(self, placeholder): + def repopulate_placeholder(self, placeholder): repre_ids = self._get_loaded_repre_ids() self.populate_load_placeholder(placeholder, repre_ids) diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index f4dfac1e32..ba0d975496 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -185,7 +185,7 @@ class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): def populate_placeholder(self, placeholder): self.populate_load_placeholder(placeholder) - def update_template_placeholder(self, placeholder): + def repopulate_placeholder(self, placeholder): repre_ids = self._get_loaded_repre_ids() self.populate_load_placeholder(placeholder, repre_ids) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 4c6f3939e5..494eebda8a 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -377,7 +377,7 @@ class AbstractTemplateBuilder(object): for placeholder in placeholders: plugin = placeholder.plugin - plugin.update_template_placeholder(placeholder) + plugin.repopulate_placeholder(placeholder) self.clear_shared_populate_data() @@ -725,7 +725,7 @@ class PlaceholderPlugin(object): pass - def update_template_placeholder(self, placeholder): + def repopulate_placeholder(self, placeholder): """Update scene with current context for passed placeholder. Can be used to re-run placeholder logic (if it make sense). From b14ab9f2aff93fce85cec7b6403183a0b7dcf511 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 12:31:44 +0200 Subject: [PATCH 320/513] added publisher to host tools --- openpype/tools/utils/host_tools.py | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 52d15a59f7..3177ed35aa 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -32,6 +32,7 @@ class HostToolsHelper: self._workfiles_tool = None self._loader_tool = None self._creator_tool = None + self._publisher_tool = None self._subset_manager_tool = None self._scene_inventory_tool = None self._library_loader_tool = None @@ -205,6 +206,7 @@ class HostToolsHelper: pyblish_show = self._discover_pyblish_gui() return pyblish_show(parent) + def _discover_pyblish_gui(self): """Return the most desirable of the currently registered GUIs""" # Prefer last registered @@ -269,6 +271,30 @@ class HostToolsHelper: dialog.activateWindow() dialog.showNormal() + def get_publisher_tool(self, parent): + """Create, cache and return scene inventory tool window.""" + if self._scene_inventory_tool is None: + from openpype.tools.publisher import PublisherWindow + + host = registered_host() + ILoadHost.validate_load_methods(host) + + publisher_window = PublisherWindow( + parent=parent or self._parent + ) + self._publisher_tool = publisher_window + + return self._publisher_tool + + def show_publisher_tool(self, parent=None): + with qt_app_context(): + dialog = self.get_publisher_tool(parent) + + dialog.show() + dialog.raise_() + dialog.activateWindow() + dialog.showNormal() + def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. @@ -298,6 +324,10 @@ class HostToolsHelper: elif tool_name == "publish": self.log.info("Can't return publish tool window.") + # "new" publisher + elif tool_name == "publisher": + return self.get_publisher_tool(parent, *args, **kwargs) + elif tool_name == "experimental_tools": return self.get_experimental_tools_dialog(parent, *args, **kwargs) @@ -335,6 +365,9 @@ class HostToolsHelper: elif tool_name == "publish": self.show_publish(parent, *args, **kwargs) + elif tool_name == "publisher": + self.show_publisher_tool(parent, *args, **kwargs) + elif tool_name == "experimental_tools": self.show_experimental_tools_dialog(parent, *args, **kwargs) @@ -414,6 +447,10 @@ def show_publish(parent=None): _SingletonPoint.show_tool_by_name("publish", parent) +def show_publisher(parent=None): + _SingletonPoint.show_tool_by_name("publisher", parent) + + def show_experimental_tools_dialog(parent=None): _SingletonPoint.show_tool_by_name("experimental_tools", parent) From 7ad8aa34db533c97de270d520874a307caa93fe4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 12:38:36 +0200 Subject: [PATCH 321/513] fix variable usage --- openpype/tools/utils/host_tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 3177ed35aa..f7e6d330ed 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -273,7 +273,8 @@ class HostToolsHelper: def get_publisher_tool(self, parent): """Create, cache and return scene inventory tool window.""" - if self._scene_inventory_tool is None: + + if self._publisher_tool is None: from openpype.tools.publisher import PublisherWindow host = registered_host() From a440a92838772c967f1bb844534153fe9814f4fc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 13 Sep 2022 13:34:37 +0200 Subject: [PATCH 322/513] Fix docstring Co-authored-by: Roy Nieterau --- openpype/tools/utils/host_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index f7e6d330ed..7208e0a500 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -272,7 +272,7 @@ class HostToolsHelper: dialog.showNormal() def get_publisher_tool(self, parent): - """Create, cache and return scene inventory tool window.""" + """Create, cache and return publisher window.""" if self._publisher_tool is None: from openpype.tools.publisher import PublisherWindow From 9e5e5d59210a82d6c171f3871834955d326b2a0b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 13:35:38 +0200 Subject: [PATCH 323/513] remove unnecessary lines --- openpype/tools/utils/host_tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 7208e0a500..d2f05d3302 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -194,7 +194,6 @@ class HostToolsHelper: library_loader_tool.showNormal() library_loader_tool.refresh() - def show_publish(self, parent=None): """Try showing the most desirable publish GUI @@ -206,7 +205,6 @@ class HostToolsHelper: pyblish_show = self._discover_pyblish_gui() return pyblish_show(parent) - def _discover_pyblish_gui(self): """Return the most desirable of the currently registered GUIs""" # Prefer last registered From ecee2d2be5d33c4014effa836f620114cfc1bf9a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 14:02:11 +0200 Subject: [PATCH 324/513] implemented 'check_ftrack_url' in ftrack module --- openpype/modules/ftrack/__init__.py | 8 ++- openpype/modules/ftrack/ftrack_module.py | 69 ++++++++++++++++++++---- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/openpype/modules/ftrack/__init__.py b/openpype/modules/ftrack/__init__.py index 7261254c6f..6dc67b74b9 100644 --- a/openpype/modules/ftrack/__init__.py +++ b/openpype/modules/ftrack/__init__.py @@ -1,9 +1,13 @@ from .ftrack_module import ( FtrackModule, - FTRACK_MODULE_DIR + FTRACK_MODULE_DIR, + + check_ftrack_url, ) __all__ = ( "FtrackModule", - "FTRACK_MODULE_DIR" + "FTRACK_MODULE_DIR", + + "check_ftrack_url", ) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index cb4f204523..e00f9d89c6 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -12,8 +12,10 @@ from openpype_interfaces import ( ISettingsChangeListener ) from openpype.settings import SaveWarningExc +from openpype.lib import Logger FTRACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) +_PLACEHOLDER = object() class FtrackModule( @@ -28,17 +30,8 @@ class FtrackModule( ftrack_settings = settings[self.name] self.enabled = ftrack_settings["enabled"] - # Add http schema - ftrack_url = ftrack_settings["ftrack_server"].strip("/ ") - if ftrack_url: - if "http" not in ftrack_url: - ftrack_url = "https://" + ftrack_url - - # Check if "ftrack.app" is part os url - if "ftrackapp.com" not in ftrack_url: - ftrack_url = ftrack_url + ".ftrackapp.com" - - self.ftrack_url = ftrack_url + self._settings_ftrack_url = ftrack_settings["ftrack_server"] + self._ftrack_url = _PLACEHOLDER current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() @@ -70,6 +63,16 @@ class FtrackModule( self.timers_manager_connector = None self._timers_manager_module = None + def get_ftrack_url(self): + if self._ftrack_url is _PLACEHOLDER: + self._ftrack_url = check_ftrack_url( + self._settings_ftrack_url, + logger=self.log + ) + return self._ftrack_url + + ftrack_url = property(get_ftrack_url) + def get_global_environments(self): """Ftrack's global environments.""" return { @@ -479,6 +482,50 @@ class FtrackModule( click_group.add_command(cli_main) +def _check_ftrack_url(url): + import requests + + try: + result = requests.get(url, allow_redirects=False) + except requests.exceptions.RequestException: + return False + + if (result.status_code != 200 or "FTRACK_VERSION" not in result.headers): + return False + return True + + +def check_ftrack_url(url, log_errors=True, logger=None): + """Checks if Ftrack server is responding""" + + if logger is None: + logger = Logger.get_logger(__name__) + + url = url.strip("/ ") + if not url: + logger.error("Ftrack URL is not set!") + return None + + if not url.startswith("http"): + url = "https://" + url + + ftrack_url = None + if not url.endswith("ftrackapp.com"): + ftrackapp_url = url + ".ftrackapp.com" + if _check_ftrack_url(ftrackapp_url): + ftrack_url = ftrackapp_url + + if not ftrack_url and _check_ftrack_url(url): + ftrack_url = url + + if ftrack_url: + logger.debug("Ftrack server \"{}\" is accessible.".format(ftrack_url)) + elif log_errors: + logger.error("Entered Ftrack URL \"{}\" is not accesible!".format(url)) + + return ftrack_url + + @click.group(FtrackModule.name, help="Ftrack module related commands.") def cli_main(): pass From b29f26b28cb9350c0460b8bd8b89a8bfcbf0c7cd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 14:02:42 +0200 Subject: [PATCH 325/513] changed imports in ftrack tray --- openpype/modules/ftrack/tray/ftrack_tray.py | 40 +++++++++------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 501d837a4c..a6a87b8ef9 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -6,22 +6,18 @@ import threading from Qt import QtCore, QtWidgets, QtGui import ftrack_api -from ..ftrack_server.lib import check_ftrack_url -from ..ftrack_server import socket_thread -from ..lib import credentials -from ..ftrack_module import FTRACK_MODULE_DIR -from . import login_dialog - from openpype import resources from openpype.lib import Logger - - -log = Logger.get_logger("FtrackModule") +from openpype_modules.ftrack import check_ftrack_url, FTRACK_MODULE_DIR +from openpype_modules.ftrack.ftrack_server import socket_thread +from openpype_modules.ftrack.lib import credentials +from . import login_dialog class FtrackTrayWrapper: def __init__(self, module): self.module = module + self.log = Logger.get_logger(self.__class__.__name__) self.thread_action_server = None self.thread_socket_server = None @@ -62,19 +58,19 @@ class FtrackTrayWrapper: if validation: self.widget_login.set_credentials(ft_user, ft_api_key) self.module.set_credentials_to_env(ft_user, ft_api_key) - log.info("Connected to Ftrack successfully") + self.log.info("Connected to Ftrack successfully") self.on_login_change() return validation if not validation and ft_user and ft_api_key: - log.warning( + self.log.warning( "Current Ftrack credentials are not valid. {}: {} - {}".format( str(os.environ.get("FTRACK_SERVER")), ft_user, ft_api_key ) ) - log.info("Please sign in to Ftrack") + self.log.info("Please sign in to Ftrack") self.bool_logged = False self.show_login_widget() self.set_menu_visibility() @@ -104,7 +100,7 @@ class FtrackTrayWrapper: self.action_credentials.setIcon(self.icon_not_logged) self.action_credentials.setToolTip("Logged out") - log.info("Logged out of Ftrack") + self.log.info("Logged out of Ftrack") self.bool_logged = False self.set_menu_visibility() @@ -126,10 +122,6 @@ class FtrackTrayWrapper: ftrack_url = self.module.ftrack_url os.environ["FTRACK_SERVER"] = ftrack_url - parent_file_path = os.path.dirname( - os.path.dirname(os.path.realpath(__file__)) - ) - min_fail_seconds = 5 max_fail_count = 3 wait_time_after_max_fail = 10 @@ -154,7 +146,7 @@ class FtrackTrayWrapper: # Main loop while True: if not self.bool_action_server_running: - log.debug("Action server was pushed to stop.") + self.log.debug("Action server was pushed to stop.") break # Check if accessible Ftrack and Mongo url @@ -164,7 +156,9 @@ class FtrackTrayWrapper: # Run threads only if Ftrack is accessible if not ftrack_accessible: if not printed_ftrack_error: - log.warning("Can't access Ftrack {}".format(ftrack_url)) + self.log.warning( + "Can't access Ftrack {}".format(ftrack_url) + ) if self.thread_socket_server is not None: self.thread_socket_server.stop() @@ -191,7 +185,7 @@ class FtrackTrayWrapper: self.set_menu_visibility() elif failed_count == max_fail_count: - log.warning(( + self.log.warning(( "Action server failed {} times." " I'll try to run again {}s later" ).format( @@ -243,10 +237,10 @@ class FtrackTrayWrapper: self.thread_action_server.join() self.thread_action_server = None - log.info("Ftrack action server was forced to stop") + self.log.info("Ftrack action server was forced to stop") except Exception: - log.warning( + self.log.warning( "Error has happened during Killing action server", exc_info=True ) @@ -343,7 +337,7 @@ class FtrackTrayWrapper: self.thread_timer = None except Exception as e: - log.error("During Killing Timer event server: {0}".format(e)) + self.log.error("During Killing Timer event server: {0}".format(e)) def changed_user(self): self.stop_action_server() From 21e050a8f18a272da3e200405550be6570e9f3d9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 14:05:25 +0200 Subject: [PATCH 326/513] use new import of 'check_ftrack_url' --- openpype/modules/ftrack/ftrack_module.py | 2 +- .../modules/ftrack/ftrack_server/__init__.py | 2 -- .../ftrack/ftrack_server/event_server_cli.py | 6 ++-- openpype/modules/ftrack/ftrack_server/lib.py | 35 +------------------ openpype/modules/ftrack/lib/avalon_sync.py | 7 ++-- 5 files changed, 8 insertions(+), 44 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index e00f9d89c6..899711e33e 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -6,7 +6,7 @@ import platform import click from openpype.modules import OpenPypeModule -from openpype_interfaces import ( +from openpype.modules.interfaces import ( ITrayModule, IPluginPaths, ISettingsChangeListener diff --git a/openpype/modules/ftrack/ftrack_server/__init__.py b/openpype/modules/ftrack/ftrack_server/__init__.py index 9e3920b500..8e5f7c4c51 100644 --- a/openpype/modules/ftrack/ftrack_server/__init__.py +++ b/openpype/modules/ftrack/ftrack_server/__init__.py @@ -1,8 +1,6 @@ from .ftrack_server import FtrackServer -from .lib import check_ftrack_url __all__ = ( "FtrackServer", - "check_ftrack_url" ) diff --git a/openpype/modules/ftrack/ftrack_server/event_server_cli.py b/openpype/modules/ftrack/ftrack_server/event_server_cli.py index 3ef7c8270a..2848469bc3 100644 --- a/openpype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/openpype/modules/ftrack/ftrack_server/event_server_cli.py @@ -20,9 +20,11 @@ from openpype.lib import ( get_openpype_version, get_build_version, ) -from openpype_modules.ftrack import FTRACK_MODULE_DIR +from openpype_modules.ftrack import ( + FTRACK_MODULE_DIR, + check_ftrack_url, +) from openpype_modules.ftrack.lib import credentials -from openpype_modules.ftrack.ftrack_server.lib import check_ftrack_url from openpype_modules.ftrack.ftrack_server import socket_thread diff --git a/openpype/modules/ftrack/ftrack_server/lib.py b/openpype/modules/ftrack/ftrack_server/lib.py index 947dacf917..c8143f739c 100644 --- a/openpype/modules/ftrack/ftrack_server/lib.py +++ b/openpype/modules/ftrack/ftrack_server/lib.py @@ -26,45 +26,12 @@ except ImportError: from openpype_modules.ftrack.lib import get_ftrack_event_mongo_info from openpype.client import OpenPypeMongoConnection -from openpype.api import Logger +from openpype.lib import Logger TOPIC_STATUS_SERVER = "openpype.event.server.status" TOPIC_STATUS_SERVER_RESULT = "openpype.event.server.status.result" -def check_ftrack_url(url, log_errors=True, logger=None): - """Checks if Ftrack server is responding""" - if logger is None: - logger = Logger.get_logger(__name__) - - if not url: - logger.error("Ftrack URL is not set!") - return None - - url = url.strip('/ ') - - if 'http' not in url: - if url.endswith('ftrackapp.com'): - url = 'https://' + url - else: - url = 'https://{0}.ftrackapp.com'.format(url) - try: - result = requests.get(url, allow_redirects=False) - except requests.exceptions.RequestException: - if log_errors: - logger.error("Entered Ftrack URL is not accesible!") - return False - - if (result.status_code != 200 or 'FTRACK_VERSION' not in result.headers): - if log_errors: - logger.error("Entered Ftrack URL is not accesible!") - return False - - logger.debug("Ftrack server {} is accessible.".format(url)) - - return url - - class SocketBaseEventHub(ftrack_api.event.hub.EventHub): hearbeat_msg = b"hearbeat" diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 72be6a8e9a..935d1e85c9 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -19,11 +19,8 @@ from openpype.client.operations import ( CURRENT_PROJECT_SCHEMA, CURRENT_PROJECT_CONFIG_SCHEMA, ) -from openpype.api import ( - Logger, - get_anatomy_settings -) -from openpype.lib import ApplicationManager +from openpype.settings import get_anatomy_settings +from openpype.lib import ApplicationManager, Logger from openpype.pipeline import AvalonMongoDB, schema from .constants import CUST_ATTR_ID_KEY, FPS_KEYS From 98f1312ce999e4be72a1a90bce99c75be967cdfd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:06:31 +0200 Subject: [PATCH 327/513] Modify log message Co-authored-by: Roy Nieterau --- openpype/modules/ftrack/ftrack_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 899711e33e..2ab0eb8239 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -521,7 +521,7 @@ def check_ftrack_url(url, log_errors=True, logger=None): if ftrack_url: logger.debug("Ftrack server \"{}\" is accessible.".format(ftrack_url)) elif log_errors: - logger.error("Entered Ftrack URL \"{}\" is not accesible!".format(url)) + logger.error("Entered Ftrack URL \"{}\" is not accessible!".format(url)) return ftrack_url From aead601397e0ebecfafb6da62570c0585f627018 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:11:20 +0200 Subject: [PATCH 328/513] unify messages --- openpype/modules/ftrack/ftrack_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 2ab0eb8239..e79910372f 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -520,8 +520,9 @@ def check_ftrack_url(url, log_errors=True, logger=None): if ftrack_url: logger.debug("Ftrack server \"{}\" is accessible.".format(ftrack_url)) + elif log_errors: - logger.error("Entered Ftrack URL \"{}\" is not accessible!".format(url)) + logger.error("Ftrack server \"{}\" is not accessible!".format(url)) return ftrack_url From 0291d2a7054b6b551fc8e5dc1092a87a026838d8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:15:18 +0200 Subject: [PATCH 329/513] renamed 'check_ftrack_url' to 'resolve_ftrack_url' --- openpype/modules/ftrack/__init__.py | 4 ++-- openpype/modules/ftrack/ftrack_module.py | 6 +++--- openpype/modules/ftrack/ftrack_server/event_server_cli.py | 8 ++++---- openpype/modules/ftrack/tray/ftrack_tray.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/modules/ftrack/__init__.py b/openpype/modules/ftrack/__init__.py index 6dc67b74b9..e520f08337 100644 --- a/openpype/modules/ftrack/__init__.py +++ b/openpype/modules/ftrack/__init__.py @@ -2,12 +2,12 @@ from .ftrack_module import ( FtrackModule, FTRACK_MODULE_DIR, - check_ftrack_url, + resolve_ftrack_url, ) __all__ = ( "FtrackModule", "FTRACK_MODULE_DIR", - "check_ftrack_url", + "resolve_ftrack_url", ) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index e79910372f..05ea7b79d1 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -65,7 +65,7 @@ class FtrackModule( def get_ftrack_url(self): if self._ftrack_url is _PLACEHOLDER: - self._ftrack_url = check_ftrack_url( + self._ftrack_url = resolve_ftrack_url( self._settings_ftrack_url, logger=self.log ) @@ -495,8 +495,8 @@ def _check_ftrack_url(url): return True -def check_ftrack_url(url, log_errors=True, logger=None): - """Checks if Ftrack server is responding""" +def resolve_ftrack_url(url, log_errors=True, logger=None): + """Checks if Ftrack server is responding.""" if logger is None: logger = Logger.get_logger(__name__) diff --git a/openpype/modules/ftrack/ftrack_server/event_server_cli.py b/openpype/modules/ftrack/ftrack_server/event_server_cli.py index 2848469bc3..20c5ab24a8 100644 --- a/openpype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/openpype/modules/ftrack/ftrack_server/event_server_cli.py @@ -22,7 +22,7 @@ from openpype.lib import ( ) from openpype_modules.ftrack import ( FTRACK_MODULE_DIR, - check_ftrack_url, + resolve_ftrack_url, ) from openpype_modules.ftrack.lib import credentials from openpype_modules.ftrack.ftrack_server import socket_thread @@ -116,7 +116,7 @@ def legacy_server(ftrack_url): while True: if not ftrack_accessible: - ftrack_accessible = check_ftrack_url(ftrack_url) + ftrack_accessible = resolve_ftrack_url(ftrack_url) # Run threads only if Ftrack is accessible if not ftrack_accessible and not printed_ftrack_error: @@ -259,7 +259,7 @@ def main_loop(ftrack_url): while True: # Check if accessible Ftrack and Mongo url if not ftrack_accessible: - ftrack_accessible = check_ftrack_url(ftrack_url) + ftrack_accessible = resolve_ftrack_url(ftrack_url) if not mongo_accessible: mongo_accessible = check_mongo_url(mongo_uri) @@ -443,7 +443,7 @@ def run_event_server( os.environ["CLOCKIFY_API_KEY"] = clockify_api_key # Check url regex and accessibility - ftrack_url = check_ftrack_url(ftrack_url) + ftrack_url = resolve_ftrack_url(ftrack_url) if not ftrack_url: print('Exiting! < Please enter Ftrack server url >') return 1 diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index a6a87b8ef9..e3c6e30ead 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -8,7 +8,7 @@ from Qt import QtCore, QtWidgets, QtGui import ftrack_api from openpype import resources from openpype.lib import Logger -from openpype_modules.ftrack import check_ftrack_url, FTRACK_MODULE_DIR +from openpype_modules.ftrack import resolve_ftrack_url, FTRACK_MODULE_DIR from openpype_modules.ftrack.ftrack_server import socket_thread from openpype_modules.ftrack.lib import credentials from . import login_dialog @@ -151,7 +151,7 @@ class FtrackTrayWrapper: # Check if accessible Ftrack and Mongo url if not ftrack_accessible: - ftrack_accessible = check_ftrack_url(ftrack_url) + ftrack_accessible = resolve_ftrack_url(ftrack_url) # Run threads only if Ftrack is accessible if not ftrack_accessible: From 09519c25a804186f9cc4afb92131d0572211f712 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:17:25 +0200 Subject: [PATCH 330/513] removed unused argument 'log_errors' --- openpype/modules/ftrack/ftrack_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 05ea7b79d1..68575009b2 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -495,7 +495,7 @@ def _check_ftrack_url(url): return True -def resolve_ftrack_url(url, log_errors=True, logger=None): +def resolve_ftrack_url(url, logger=None): """Checks if Ftrack server is responding.""" if logger is None: @@ -521,7 +521,7 @@ def resolve_ftrack_url(url, log_errors=True, logger=None): if ftrack_url: logger.debug("Ftrack server \"{}\" is accessible.".format(ftrack_url)) - elif log_errors: + else: logger.error("Ftrack server \"{}\" is not accessible!".format(url)) return ftrack_url From 477266f1407e84e2ba9d086107e15e8fc5173e79 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:18:56 +0200 Subject: [PATCH 331/513] better variable name for ftrack url value check --- openpype/modules/ftrack/ftrack_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 68575009b2..75ffd7f864 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -15,7 +15,7 @@ from openpype.settings import SaveWarningExc from openpype.lib import Logger FTRACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -_PLACEHOLDER = object() +_URL_NOT_SET = object() class FtrackModule( @@ -31,7 +31,7 @@ class FtrackModule( self.enabled = ftrack_settings["enabled"] self._settings_ftrack_url = ftrack_settings["ftrack_server"] - self._ftrack_url = _PLACEHOLDER + self._ftrack_url = _URL_NOT_SET current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() @@ -64,7 +64,7 @@ class FtrackModule( self._timers_manager_module = None def get_ftrack_url(self): - if self._ftrack_url is _PLACEHOLDER: + if self._ftrack_url is _URL_NOT_SET: self._ftrack_url = resolve_ftrack_url( self._settings_ftrack_url, logger=self.log From 6c2c161ed4cb570085b8ff6aada053bc4e1daf11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:30:31 +0200 Subject: [PATCH 332/513] moved exceptions to workfile_template_builder --- .../maya/api/workfile_template_builder.py | 4 +-- .../workfile/workfile_template_builder.py | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 5fd2113bdb..71e3e0ce4e 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -3,10 +3,8 @@ import json from maya import cmds from openpype.pipeline import registered_host -from openpype.pipeline.workfile.build_template_exceptions import ( - TemplateAlreadyImported -) from openpype.pipeline.workfile.workfile_template_builder import ( + TemplateAlreadyImported, AbstractTemplateBuilder, PlaceholderPlugin, PlaceholderItem, diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 494eebda8a..a381b96c8f 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -28,11 +28,27 @@ from openpype.pipeline.load import ( ) from openpype.pipeline.create import get_legacy_creator_by_name -from .build_template_exceptions import ( - TemplateProfileNotFound, - TemplateLoadingFailed, - TemplateNotFound, -) + +class TemplateNotFound(Exception): + """Exception raised when template does not exist.""" + pass + + +class TemplateProfileNotFound(Exception): + """Exception raised when current profile + doesn't match any template profile""" + pass + + +class TemplateAlreadyImported(Exception): + """Error raised when Template was already imported by host for + this session""" + pass + + +class TemplateLoadFailed(Exception): + """Error raised whend Template loader was unable to load the template""" + pass @six.add_metaclass(ABCMeta) From 778e0b2e491f5948f2932968a70f8f620204fb01 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 15:44:20 +0200 Subject: [PATCH 333/513] Perform case-insensitive lookup --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 7e5815b100..5d39e12985 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -74,11 +74,14 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): version_number = int(instance_version) family = instance.data["family"] - family_low = family.lower() + # Perform case-insensitive family mapping + family_low = family.lower() asset_type = instance.data.get("ftrackFamily") - if not asset_type and family_low in self.family_mapping: - asset_type = self.family_mapping[family_low] + if not asset_type: + for map_family, map_value in self.family_mapping.items(): + if map_family.lower() == family_low: + asset_type = map_value if not asset_type: asset_type = "upload" From 4466d8a94249ad66546730b7135e34003f4aa4f8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 15:45:11 +0200 Subject: [PATCH 334/513] Remove redundant logic since just above it's forced to be "upload" when `not asset_type` --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 5d39e12985..a35dbf71d4 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -89,15 +89,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): self.log.debug( "Family: {}\nMapping: {}".format(family_low, self.family_mapping) ) - - # Ignore this instance if neither "ftrackFamily" or a family mapping is - # found. - if not asset_type: - self.log.info(( - "Family \"{}\" does not match any asset type mapping" - ).format(family)) - return - status_name = self._get_asset_version_status_name(instance) # Base of component item data From 1bc37ace465f647b6af35a4a2b8cf2832bd94925 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 15:45:48 +0200 Subject: [PATCH 335/513] Actually break loop early on detected mapping --- .../modules/ftrack/plugins/publish/integrate_ftrack_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index a35dbf71d4..5ff75e7060 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -82,6 +82,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): for map_family, map_value in self.family_mapping.items(): if map_family.lower() == family_low: asset_type = map_value + break if not asset_type: asset_type = "upload" From b2b1613016f54d8293296c2f6cca5a87a9b62565 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 16:19:52 +0200 Subject: [PATCH 336/513] fix raised exception --- openpype/pipeline/workfile/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index a381b96c8f..2358a047f1 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -560,7 +560,7 @@ class AbstractTemplateBuilder(object): path = profile["path"] if not path: - raise TemplateLoadingFailed(( + raise TemplateLoadFailed(( "Template path is not set.\n" "Path need to be set in {}\\Template Workfile Build " "Settings\\Profiles" From fe2a769a7e3b5b8e6fc0744054c711a0e019ec72 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 16:20:27 +0200 Subject: [PATCH 337/513] added quick access to settings --- .../workfile/workfile_template_builder.py | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 2358a047f1..28c06aeeac 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -11,7 +11,10 @@ from openpype.client import ( get_linked_assets, get_representations, ) -from openpype.settings import get_project_settings +from openpype.settings import ( + get_project_settings, + get_system_settings, +) from openpype.host import HostBase from openpype.lib import ( Logger, @@ -86,6 +89,9 @@ class AbstractTemplateBuilder(object): self._loaders_by_name = None self._creators_by_name = None + self._system_settings = None + self._project_settings = None + self._current_asset_doc = None self._linked_asset_docs = None self._task_type = None @@ -102,6 +108,18 @@ class AbstractTemplateBuilder(object): def current_task_name(self): return legacy_io.Session["AVALON_TASK"] + @property + def system_settings(self): + if self._system_settings is None: + self._system_settings = get_system_settings() + return self._system_settings + + @property + def project_settings(self): + if self._project_settings is None: + self._project_settings = get_project_settings(self.project_name) + return self._project_settings + @property def current_asset_doc(self): if self._current_asset_doc is None: @@ -184,6 +202,9 @@ class AbstractTemplateBuilder(object): self._linked_asset_docs = None self._task_type = None + self._system_settings = None + self._project_settings = None + self.clear_shared_data() self.clear_shared_populate_data() @@ -529,9 +550,8 @@ class AbstractTemplateBuilder(object): self.refresh() def _get_build_profiles(self): - project_settings = get_project_settings(self.project_name) return ( - project_settings + self.project_settings [self.host_name] ["templated_workfile_build"] ["profiles"] From a0ffa97e1d125642f575a26f888e6b8469ea8230 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 16:20:44 +0200 Subject: [PATCH 338/513] added some docstrings --- openpype/pipeline/workfile/build_workfile.py | 11 ++ .../workfile/workfile_template_builder.py | 117 ++++++++++++++++-- 2 files changed, 121 insertions(+), 7 deletions(-) diff --git a/openpype/pipeline/workfile/build_workfile.py b/openpype/pipeline/workfile/build_workfile.py index 0b8a444436..87b9df158f 100644 --- a/openpype/pipeline/workfile/build_workfile.py +++ b/openpype/pipeline/workfile/build_workfile.py @@ -1,3 +1,14 @@ +"""Workfile build based on settings. + +Workfile builder will do stuff based on project settings. Advantage is that +it need only access to settings. Disadvantage is that it is hard to focus +build per context and being explicit about loaded content. + +For more explicit workfile build is recommended 'AbstractTemplateBuilder' +from '~/openpype/pipeline/workfile/workfile_template_builder'. Which gives +more abilities to define how build happens but require more code to achive it. +""" + import os import re import collections diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 28c06aeeac..f81849fbe4 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -1,3 +1,16 @@ +"""Workfile build mechanism using workfile templates. + +Build templates are manually prepared using plugin definitions which create +placeholders inside the template which are populated on import. + +This approach is very explicit to achive very specific build logic that can be +targeted by task types and names. + +Placeholders are created using placeholder plugins which should care about +logic and data of placeholder items. 'PlaceholderItem' is used to keep track +about it's progress. +""" + import os import re import collections @@ -64,6 +77,13 @@ class AbstractTemplateBuilder(object): Rest of logic is based on plugins that care about collection and creation of placeholder items. + Population of placeholders happens in loops. Each loop will collect all + available placeholders, skip already populated, and populate the rest. + + Builder item has 2 types of shared data. Refresh lifetime which are cleared + on refresh and populate lifetime which are cleared after loop of + placeholder population. + Args: host (Union[HostBase, ModuleType]): Implementation of host. """ @@ -382,6 +402,20 @@ class AbstractTemplateBuilder(object): )) def build_template(self, template_path=None, level_limit=None): + """Main callback for building workfile from template path. + + Todo: + Handle report of populated placeholders from + 'populate_scene_placeholders' to be shown to a user. + + Args: + template_path (str): Path to a template file with placeholders. + Template from settings 'get_template_path' used when not + passed. + level_limit (int): Limit of populate loops. Related to + 'populate_scene_placeholders' method. + """ + if template_path is None: template_path = self.get_template_path() self.import_template(template_path) @@ -492,6 +526,8 @@ class AbstractTemplateBuilder(object): for placeholder in placeholders } all_processed = len(placeholders) == 0 + # Counter is checked at the ned of a loop so the loop happens at least + # once. iter_counter = 0 while not all_processed: filtered_placeholders = [] @@ -550,6 +586,12 @@ class AbstractTemplateBuilder(object): self.refresh() def _get_build_profiles(self): + """Get build profiles for workfile build template path. + + Returns: + List[Dict[str, Any]]: Profiles for template path resolving. + """ + return ( self.project_settings [self.host_name] @@ -558,6 +600,22 @@ class AbstractTemplateBuilder(object): ) def get_template_path(self): + """Unified way how template path is received usign settings. + + Method is dependent on '_get_build_profiles' which should return filter + profiles to resolve path to a template. Default implementation looks + into host settings: + - 'project_settings/{host name}/templated_workfile_build/profiles' + + Returns: + str: Path to a template file with placeholders. + + Raises: + TemplateProfileNotFound: When profiles are not filled. + TemplateLoadFailed: Profile was found but path is not set. + TemplateNotFound: Path was set but file does not exists. + """ + host_name = self.host_name project_name = self.project_name task_name = self.current_task_name @@ -630,6 +688,14 @@ class AbstractTemplateBuilder(object): @six.add_metaclass(ABCMeta) class PlaceholderPlugin(object): + """Plugin which care about handling of placeholder items logic. + + Plugin create and update placeholders in scene and populate them on + template import. Populating means that based on placeholder data happens + a logic in the scene. Most common logic is to load representation using + loaders or to create instances in scene. + """ + label = None _log = None @@ -641,7 +707,7 @@ class PlaceholderPlugin(object): """Access to builder which initialized the plugin. Returns: - AbstractTemplateLoader: Loader of template build. + AbstractTemplateBuilder: Loader of template build. """ return self._builder @@ -852,8 +918,12 @@ class PlaceholderPlugin(object): class PlaceholderItem(object): """Item representing single item in scene that is a placeholder to process. + Items are always created and updated by their plugins. Each plugin can use + modified class of 'PlacehoderItem' but only to add more options instead of + new other. + Scene identifier is used to avoid processing of the palceholder item - multiple times. + multiple times so must be unique across whole workfile builder. Args: scene_identifier (str): Unique scene identifier. If placeholder is @@ -893,7 +963,7 @@ class PlaceholderItem(object): """Access to builder. Returns: - AbstractTemplateLoader: Builder which is the top part of + AbstractTemplateBuilder: Builder which is the top part of placeholder. """ @@ -936,6 +1006,8 @@ class PlaceholderItem(object): @property def order(self): + """Order of item processing.""" + order = self._data.get("order") if order is None: return self.default_order @@ -1160,7 +1232,25 @@ class PlaceholderLoadMixin(object): return {} - def get_representations(self, placeholder): + def _get_representations(self, placeholder): + """Prepared query of representations based on load options. + + This function is directly connected to options defined in + 'get_load_plugin_options'. + + Note: + This returns all representation documents from all versions of + matching subset. To filter for last version use + '_reduce_last_version_repre_docs'. + + Args: + placeholder (PlaceholderItem): Item which should be populated. + + Returns: + List[Dict[str, Any]]: Representation documents matching filters + from placeholder data. + """ + project_name = self.builder.project_name current_asset_doc = self.builder.current_asset_doc linked_asset_docs = self.builder.linked_asset_docs @@ -1263,7 +1353,7 @@ class PlaceholderLoadMixin(object): loader_name = placeholder.data["loader"] loader_args = placeholder.data["loader_args"] - placeholder_representations = self.get_representations(placeholder) + placeholder_representations = self._get_representations(placeholder) filtered_representations = [] for representation in self._reduce_last_version_repre_docs( @@ -1306,11 +1396,24 @@ class PlaceholderLoadMixin(object): ) except Exception: + failed = True placeholder.load_failed(representation) else: + failed = False placeholder.load_succeed(container) - self.cleanup_placeholder(placeholder) + self.cleanup_placeholder(placeholder, failed) + + def cleanup_placeholder(self, placeholder, failed): + """Cleanup placeholder after load of single representation. + + Can be called multiple times during placeholder item populating and is + called even if loading failed. + + Args: + placeholder (PlaceholderItem): Item which was just used to load + representation. + failed (bool): Loading of representation failed. + """ - def cleanup_placeholder(self, placeholder): pass From 60c1d1eb6c1ea42bd974cfd528dc9ce3cfda0b3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 16:21:46 +0200 Subject: [PATCH 339/513] removed nuke previous template builder --- .../hosts/nuke/api/lib_template_builder.py | 220 ------ openpype/hosts/nuke/api/template_loader.py | 639 ------------------ 2 files changed, 859 deletions(-) delete mode 100644 openpype/hosts/nuke/api/lib_template_builder.py delete mode 100644 openpype/hosts/nuke/api/template_loader.py diff --git a/openpype/hosts/nuke/api/lib_template_builder.py b/openpype/hosts/nuke/api/lib_template_builder.py deleted file mode 100644 index 61baa23928..0000000000 --- a/openpype/hosts/nuke/api/lib_template_builder.py +++ /dev/null @@ -1,220 +0,0 @@ -from collections import OrderedDict - -import qargparse - -import nuke - -from openpype.tools.utils.widgets import OptionDialog - -from .lib import imprint, get_main_window - - -# To change as enum -build_types = ["context_asset", "linked_asset", "all_assets"] - - -def get_placeholder_attributes(node, enumerate=False): - list_atts = { - "builder_type", - "family", - "representation", - "loader", - "loader_args", - "order", - "asset", - "subset", - "hierarchy", - "siblings", - "last_loaded" - } - attributes = {} - for attr in node.knobs().keys(): - if attr in list_atts: - if enumerate: - try: - attributes[attr] = node.knob(attr).values() - except AttributeError: - attributes[attr] = node.knob(attr).getValue() - else: - attributes[attr] = node.knob(attr).getValue() - - return attributes - - -def delete_placeholder_attributes(node): - """Delete all extra placeholder attributes.""" - - extra_attributes = get_placeholder_attributes(node) - for attribute in extra_attributes.keys(): - try: - node.removeKnob(node.knob(attribute)) - except ValueError: - continue - - -def hide_placeholder_attributes(node): - """Hide all extra placeholder attributes.""" - - extra_attributes = get_placeholder_attributes(node) - for attribute in extra_attributes.keys(): - try: - node.knob(attribute).setVisible(False) - except ValueError: - continue - - -def create_placeholder(): - args = placeholder_window() - if not args: - # operation canceled, no locator created - return - - placeholder = nuke.nodes.NoOp() - placeholder.setName("PLACEHOLDER") - placeholder.knob("tile_color").setValue(4278190335) - - # custom arg parse to force empty data query - # and still imprint them on placeholder - # and getting items when arg is of type Enumerator - options = OrderedDict() - for arg in args: - if not type(arg) == qargparse.Separator: - options[str(arg)] = arg._data.get("items") or arg.read() - imprint(placeholder, options) - imprint(placeholder, {"is_placeholder": True}) - placeholder.knob("is_placeholder").setVisible(False) - - -def update_placeholder(): - placeholder = nuke.selectedNodes() - if not placeholder: - raise ValueError("No node selected") - if len(placeholder) > 1: - raise ValueError("Too many selected nodes") - placeholder = placeholder[0] - - args = placeholder_window(get_placeholder_attributes(placeholder)) - if not args: - return # operation canceled - # delete placeholder attributes - delete_placeholder_attributes(placeholder) - - options = OrderedDict() - for arg in args: - if not type(arg) == qargparse.Separator: - options[str(arg)] = arg._data.get("items") or arg.read() - imprint(placeholder, options) - - -def imprint_enum(placeholder, args): - """ - Imprint method doesn't act properly with enums. - Replacing the functionnality with this for now - """ - - enum_values = { - str(arg): arg.read() - for arg in args - if arg._data.get("items") - } - string_to_value_enum_table = { - build: idx - for idx, build in enumerate(build_types) - } - attrs = {} - for key, value in enum_values.items(): - attrs[key] = string_to_value_enum_table[value] - - -def placeholder_window(options=None): - options = options or dict() - dialog = OptionDialog(parent=get_main_window()) - dialog.setWindowTitle("Create Placeholder") - - args = [ - qargparse.Separator("Main attributes"), - qargparse.Enum( - "builder_type", - label="Asset Builder Type", - default=options.get("builder_type", 0), - items=build_types, - help="""Asset Builder Type -Builder type describe what template loader will look for. - -context_asset : Template loader will look for subsets of -current context asset (Asset bob will find asset) - -linked_asset : Template loader will look for assets linked -to current context asset. -Linked asset are looked in OpenPype database under field "inputLinks" -""" - ), - qargparse.String( - "family", - default=options.get("family", ""), - label="OpenPype Family", - placeholder="ex: image, plate ..."), - qargparse.String( - "representation", - default=options.get("representation", ""), - label="OpenPype Representation", - placeholder="ex: mov, png ..."), - qargparse.String( - "loader", - default=options.get("loader", ""), - label="Loader", - placeholder="ex: LoadClip, LoadImage ...", - help="""Loader - -Defines what openpype loader will be used to load assets. -Useable loader depends on current host's loader list. -Field is case sensitive. -"""), - qargparse.String( - "loader_args", - default=options.get("loader_args", ""), - label="Loader Arguments", - placeholder='ex: {"camera":"persp", "lights":True}', - help="""Loader - -Defines a dictionnary of arguments used to load assets. -Useable arguments depend on current placeholder Loader. -Field should be a valid python dict. Anything else will be ignored. -"""), - qargparse.Integer( - "order", - default=options.get("order", 0), - min=0, - max=999, - label="Order", - placeholder="ex: 0, 100 ... (smallest order loaded first)", - help="""Order - -Order defines asset loading priority (0 to 999) -Priority rule is : "lowest is first to load"."""), - qargparse.Separator( - "Optional attributes "), - qargparse.String( - "asset", - default=options.get("asset", ""), - label="Asset filter", - placeholder="regex filtering by asset name", - help="Filtering assets by matching field regex to asset's name"), - qargparse.String( - "subset", - default=options.get("subset", ""), - label="Subset filter", - placeholder="regex filtering by subset name", - help="Filtering assets by matching field regex to subset's name"), - qargparse.String( - "hierarchy", - default=options.get("hierarchy", ""), - label="Hierarchy filter", - placeholder="regex filtering by asset's hierarchy", - help="Filtering assets by matching field asset's hierarchy") - ] - dialog.create(args) - if not dialog.exec_(): - return None - - return args diff --git a/openpype/hosts/nuke/api/template_loader.py b/openpype/hosts/nuke/api/template_loader.py deleted file mode 100644 index 5ff4b8fc41..0000000000 --- a/openpype/hosts/nuke/api/template_loader.py +++ /dev/null @@ -1,639 +0,0 @@ -import re -import collections - -import nuke - -from openpype.client import get_representations -from openpype.pipeline import legacy_io -from openpype.pipeline.workfile.abstract_template_loader import ( - AbstractPlaceholder, - AbstractTemplateLoader, -) - -from .lib import ( - find_free_space_to_paste_nodes, - get_extreme_positions, - get_group_io_nodes, - imprint, - refresh_node, - refresh_nodes, - reset_selection, - get_names_from_nodes, - get_nodes_by_names, - select_nodes, - duplicate_node, - node_tempfile, -) - -from .lib_template_builder import ( - delete_placeholder_attributes, - get_placeholder_attributes, - hide_placeholder_attributes -) - -PLACEHOLDER_SET = "PLACEHOLDERS_SET" - - -class NukeTemplateLoader(AbstractTemplateLoader): - """Concrete implementation of AbstractTemplateLoader for Nuke - - """ - - def import_template(self, path): - """Import template into current scene. - Block if a template is already loaded. - - Args: - path (str): A path to current template (usually given by - get_template_path implementation) - - Returns: - bool: Wether the template was succesfully imported or not - """ - - # TODO check if the template is already imported - - nuke.nodePaste(path) - reset_selection() - - return True - - def preload(self, placeholder, loaders_by_name, last_representation): - placeholder.data["nodes_init"] = nuke.allNodes() - placeholder.data["last_repre_id"] = str(last_representation["_id"]) - - def populate_template(self, ignored_ids=None): - processed_key = "_node_processed" - - processed_nodes = [] - nodes = self.get_template_nodes() - while nodes: - # Mark nodes as processed so they're not re-executed - # - that can happen if processing of placeholder node fails - for node in nodes: - imprint(node, {processed_key: True}) - processed_nodes.append(node) - - super(NukeTemplateLoader, self).populate_template(ignored_ids) - - # Recollect nodes to repopulate - nodes = [] - for node in self.get_template_nodes(): - # Skip already processed nodes - if ( - processed_key in node.knobs() - and node.knob(processed_key).value() - ): - continue - nodes.append(node) - - for node in processed_nodes: - knob = node.knob(processed_key) - if knob is not None: - node.removeKnob(knob) - - @staticmethod - def get_template_nodes(): - placeholders = [] - all_groups = collections.deque() - all_groups.append(nuke.thisGroup()) - while all_groups: - group = all_groups.popleft() - for node in group.nodes(): - if isinstance(node, nuke.Group): - all_groups.append(node) - - node_knobs = node.knobs() - if ( - "builder_type" not in node_knobs - or "is_placeholder" not in node_knobs - or not node.knob("is_placeholder").value() - ): - continue - - if "empty" in node_knobs and node.knob("empty").value(): - continue - - placeholders.append(node) - - return placeholders - - def update_missing_containers(self): - nodes_by_id = collections.defaultdict(list) - - for node in nuke.allNodes(): - node_knobs = node.knobs().keys() - if "repre_id" in node_knobs: - repre_id = node.knob("repre_id").getValue() - nodes_by_id[repre_id].append(node.name()) - - if "empty" in node_knobs: - node.removeKnob(node.knob("empty")) - imprint(node, {"empty": False}) - - for node_names in nodes_by_id.values(): - node = None - for node_name in node_names: - node_by_name = nuke.toNode(node_name) - if "builder_type" in node_by_name.knobs().keys(): - node = node_by_name - break - - if node is None: - continue - - placeholder = nuke.nodes.NoOp() - placeholder.setName("PLACEHOLDER") - placeholder.knob("tile_color").setValue(4278190335) - attributes = get_placeholder_attributes(node, enumerate=True) - imprint(placeholder, attributes) - pos_x = int(node.knob("x").getValue()) - pos_y = int(node.knob("y").getValue()) - placeholder.setXYpos(pos_x, pos_y) - imprint(placeholder, {"nb_children": 1}) - refresh_node(placeholder) - - self.populate_template(self.get_loaded_containers_by_id()) - - def get_loaded_containers_by_id(self): - repre_ids = set() - for node in nuke.allNodes(): - if "repre_id" in node.knobs(): - repre_ids.add(node.knob("repre_id").getValue()) - - # Removes duplicates in the list - return list(repre_ids) - - def delete_placeholder(self, placeholder): - placeholder_node = placeholder.data["node"] - last_loaded = placeholder.data["last_loaded"] - if not placeholder.data["delete"]: - if "empty" in placeholder_node.knobs().keys(): - placeholder_node.removeKnob(placeholder_node.knob("empty")) - imprint(placeholder_node, {"empty": True}) - return - - if not last_loaded: - nuke.delete(placeholder_node) - return - - if "last_loaded" in placeholder_node.knobs().keys(): - for node_name in placeholder_node.knob("last_loaded").values(): - node = nuke.toNode(node_name) - try: - delete_placeholder_attributes(node) - except Exception: - pass - - last_loaded_names = [ - loaded_node.name() - for loaded_node in last_loaded - ] - imprint(placeholder_node, {"last_loaded": last_loaded_names}) - - for node in last_loaded: - refresh_node(node) - refresh_node(placeholder_node) - if "builder_type" not in node.knobs().keys(): - attributes = get_placeholder_attributes(placeholder_node, True) - imprint(node, attributes) - imprint(node, {"is_placeholder": False}) - hide_placeholder_attributes(node) - node.knob("is_placeholder").setVisible(False) - imprint( - node, - { - "x": placeholder_node.xpos(), - "y": placeholder_node.ypos() - } - ) - node.knob("x").setVisible(False) - node.knob("y").setVisible(False) - nuke.delete(placeholder_node) - - -class NukePlaceholder(AbstractPlaceholder): - """Concrete implementation of AbstractPlaceholder for Nuke""" - - optional_keys = {"asset", "subset", "hierarchy"} - - def get_data(self, node): - user_data = dict() - node_knobs = node.knobs() - for attr in self.required_keys.union(self.optional_keys): - if attr in node_knobs: - user_data[attr] = node_knobs[attr].getValue() - user_data["node"] = node - - nb_children = 0 - if "nb_children" in node_knobs: - nb_children = int(node_knobs["nb_children"].getValue()) - user_data["nb_children"] = nb_children - - siblings = [] - if "siblings" in node_knobs: - siblings = node_knobs["siblings"].values() - user_data["siblings"] = siblings - - node_full_name = node.fullName() - user_data["group_name"] = node_full_name.rpartition(".")[0] - user_data["last_loaded"] = [] - user_data["delete"] = False - self.data = user_data - - def parent_in_hierarchy(self, containers): - return - - def create_sib_copies(self): - """ creating copies of the palce_holder siblings (the ones who were - loaded with it) for the new nodes added - - Returns : - copies (dict) : with copied nodes names and their copies - """ - - copies = {} - siblings = get_nodes_by_names(self.data["siblings"]) - for node in siblings: - new_node = duplicate_node(node) - - x_init = int(new_node.knob("x_init").getValue()) - y_init = int(new_node.knob("y_init").getValue()) - new_node.setXYpos(x_init, y_init) - if isinstance(new_node, nuke.BackdropNode): - w_init = new_node.knob("w_init").getValue() - h_init = new_node.knob("h_init").getValue() - new_node.knob("bdwidth").setValue(w_init) - new_node.knob("bdheight").setValue(h_init) - refresh_node(node) - - if "repre_id" in node.knobs().keys(): - node.removeKnob(node.knob("repre_id")) - copies[node.name()] = new_node - return copies - - def fix_z_order(self): - """Fix the problem of z_order when a backdrop is loaded.""" - - nodes_loaded = self.data["last_loaded"] - loaded_backdrops = [] - bd_orders = set() - for node in nodes_loaded: - if isinstance(node, nuke.BackdropNode): - loaded_backdrops.append(node) - bd_orders.add(node.knob("z_order").getValue()) - - if not bd_orders: - return - - sib_orders = set() - for node_name in self.data["siblings"]: - node = nuke.toNode(node_name) - if isinstance(node, nuke.BackdropNode): - sib_orders.add(node.knob("z_order").getValue()) - - if not sib_orders: - return - - min_order = min(bd_orders) - max_order = max(sib_orders) - for backdrop_node in loaded_backdrops: - z_order = backdrop_node.knob("z_order").getValue() - backdrop_node.knob("z_order").setValue( - z_order + max_order - min_order + 1) - - def update_nodes(self, nodes, considered_nodes, offset_y=None): - """Adjust backdrop nodes dimensions and positions. - - Considering some nodes sizes. - - Args: - nodes (list): list of nodes to update - considered_nodes (list): list of nodes to consider while updating - positions and dimensions - offset (int): distance between copies - """ - - placeholder_node = self.data["node"] - - min_x, min_y, max_x, max_y = get_extreme_positions(considered_nodes) - - diff_x = diff_y = 0 - contained_nodes = [] # for backdrops - - if offset_y is None: - width_ph = placeholder_node.screenWidth() - height_ph = placeholder_node.screenHeight() - diff_y = max_y - min_y - height_ph - diff_x = max_x - min_x - width_ph - contained_nodes = [placeholder_node] - min_x = placeholder_node.xpos() - min_y = placeholder_node.ypos() - else: - siblings = get_nodes_by_names(self.data["siblings"]) - minX, _, maxX, _ = get_extreme_positions(siblings) - diff_y = max_y - min_y + 20 - diff_x = abs(max_x - min_x - maxX + minX) - contained_nodes = considered_nodes - - if diff_y <= 0 and diff_x <= 0: - return - - for node in nodes: - refresh_node(node) - - if ( - node == placeholder_node - or node in considered_nodes - ): - continue - - if ( - not isinstance(node, nuke.BackdropNode) - or ( - isinstance(node, nuke.BackdropNode) - and not set(contained_nodes) <= set(node.getNodes()) - ) - ): - if offset_y is None and node.xpos() >= min_x: - node.setXpos(node.xpos() + diff_x) - - if node.ypos() >= min_y: - node.setYpos(node.ypos() + diff_y) - - else: - width = node.screenWidth() - height = node.screenHeight() - node.knob("bdwidth").setValue(width + diff_x) - node.knob("bdheight").setValue(height + diff_y) - - refresh_node(node) - - def imprint_inits(self): - """Add initial positions and dimensions to the attributes""" - - for node in nuke.allNodes(): - refresh_node(node) - imprint(node, {"x_init": node.xpos(), "y_init": node.ypos()}) - node.knob("x_init").setVisible(False) - node.knob("y_init").setVisible(False) - width = node.screenWidth() - height = node.screenHeight() - if "bdwidth" in node.knobs(): - imprint(node, {"w_init": width, "h_init": height}) - node.knob("w_init").setVisible(False) - node.knob("h_init").setVisible(False) - refresh_node(node) - - def imprint_siblings(self): - """ - - add siblings names to placeholder attributes (nodes loaded with it) - - add Id to the attributes of all the other nodes - """ - - loaded_nodes = self.data["last_loaded"] - loaded_nodes_set = set(loaded_nodes) - data = {"repre_id": str(self.data["last_repre_id"])} - - for node in loaded_nodes: - node_knobs = node.knobs() - if "builder_type" not in node_knobs: - # save the id of representation for all imported nodes - imprint(node, data) - node.knob("repre_id").setVisible(False) - refresh_node(node) - continue - - if ( - "is_placeholder" not in node_knobs - or ( - "is_placeholder" in node_knobs - and node.knob("is_placeholder").value() - ) - ): - siblings = list(loaded_nodes_set - {node}) - siblings_name = get_names_from_nodes(siblings) - siblings = {"siblings": siblings_name} - imprint(node, siblings) - - def set_loaded_connections(self): - """ - set inputs and outputs of loaded nodes""" - - placeholder_node = self.data["node"] - input_node, output_node = get_group_io_nodes(self.data["last_loaded"]) - for node in placeholder_node.dependent(): - for idx in range(node.inputs()): - if node.input(idx) == placeholder_node: - node.setInput(idx, output_node) - - for node in placeholder_node.dependencies(): - for idx in range(placeholder_node.inputs()): - if placeholder_node.input(idx) == node: - input_node.setInput(0, node) - - def set_copies_connections(self, copies): - """Set inputs and outputs of the copies. - - Args: - copies (dict): Copied nodes by their names. - """ - - last_input, last_output = get_group_io_nodes(self.data["last_loaded"]) - siblings = get_nodes_by_names(self.data["siblings"]) - siblings_input, siblings_output = get_group_io_nodes(siblings) - copy_input = copies[siblings_input.name()] - copy_output = copies[siblings_output.name()] - - for node_init in siblings: - if node_init == siblings_output: - continue - - node_copy = copies[node_init.name()] - for node in node_init.dependent(): - for idx in range(node.inputs()): - if node.input(idx) != node_init: - continue - - if node in siblings: - copies[node.name()].setInput(idx, node_copy) - else: - last_input.setInput(0, node_copy) - - for node in node_init.dependencies(): - for idx in range(node_init.inputs()): - if node_init.input(idx) != node: - continue - - if node_init == siblings_input: - copy_input.setInput(idx, node) - elif node in siblings: - node_copy.setInput(idx, copies[node.name()]) - else: - node_copy.setInput(idx, last_output) - - siblings_input.setInput(0, copy_output) - - def move_to_placeholder_group(self, nodes_loaded): - """ - opening the placeholder's group and copying loaded nodes in it. - - Returns : - nodes_loaded (list): the new list of pasted nodes - """ - - groups_name = self.data["group_name"] - reset_selection() - select_nodes(nodes_loaded) - if groups_name: - with node_tempfile() as filepath: - nuke.nodeCopy(filepath) - for node in nuke.selectedNodes(): - nuke.delete(node) - group = nuke.toNode(groups_name) - group.begin() - nuke.nodePaste(filepath) - nodes_loaded = nuke.selectedNodes() - return nodes_loaded - - def clean(self): - # deselect all selected nodes - placeholder_node = self.data["node"] - - # getting the latest nodes added - nodes_init = self.data["nodes_init"] - nodes_loaded = list(set(nuke.allNodes()) - set(nodes_init)) - self.log.debug("Loaded nodes: {}".format(nodes_loaded)) - if not nodes_loaded: - return - - self.data["delete"] = True - - nodes_loaded = self.move_to_placeholder_group(nodes_loaded) - self.data["last_loaded"] = nodes_loaded - refresh_nodes(nodes_loaded) - - # positioning of the loaded nodes - min_x, min_y, _, _ = get_extreme_positions(nodes_loaded) - for node in nodes_loaded: - xpos = (node.xpos() - min_x) + placeholder_node.xpos() - ypos = (node.ypos() - min_y) + placeholder_node.ypos() - node.setXYpos(xpos, ypos) - refresh_nodes(nodes_loaded) - - self.fix_z_order() # fix the problem of z_order for backdrops - self.imprint_siblings() - - if self.data["nb_children"] == 0: - # save initial nodes postions and dimensions, update them - # and set inputs and outputs of loaded nodes - - self.imprint_inits() - self.update_nodes(nuke.allNodes(), nodes_loaded) - self.set_loaded_connections() - - elif self.data["siblings"]: - # create copies of placeholder siblings for the new loaded nodes, - # set their inputs and outpus and update all nodes positions and - # dimensions and siblings names - - siblings = get_nodes_by_names(self.data["siblings"]) - refresh_nodes(siblings) - copies = self.create_sib_copies() - new_nodes = list(copies.values()) # copies nodes - self.update_nodes(new_nodes, nodes_loaded) - placeholder_node.removeKnob(placeholder_node.knob("siblings")) - new_nodes_name = get_names_from_nodes(new_nodes) - imprint(placeholder_node, {"siblings": new_nodes_name}) - self.set_copies_connections(copies) - - self.update_nodes( - nuke.allNodes(), - new_nodes + nodes_loaded, - 20 - ) - - new_siblings = get_names_from_nodes(new_nodes) - self.data["siblings"] = new_siblings - - else: - # if the placeholder doesn't have siblings, the loaded - # nodes will be placed in a free space - - xpointer, ypointer = find_free_space_to_paste_nodes( - nodes_loaded, direction="bottom", offset=200 - ) - node = nuke.createNode("NoOp") - reset_selection() - nuke.delete(node) - for node in nodes_loaded: - xpos = (node.xpos() - min_x) + xpointer - ypos = (node.ypos() - min_y) + ypointer - node.setXYpos(xpos, ypos) - - self.data["nb_children"] += 1 - reset_selection() - # go back to root group - nuke.root().begin() - - def get_representations(self, current_asset_doc, linked_asset_docs): - project_name = legacy_io.active_project() - - builder_type = self.data["builder_type"] - if builder_type == "context_asset": - context_filters = { - "asset": [re.compile(self.data["asset"])], - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representations": [self.data["representation"]], - "family": [self.data["family"]] - } - - elif builder_type != "linked_asset": - context_filters = { - "asset": [ - current_asset_doc["name"], - re.compile(self.data["asset"]) - ], - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representation": [self.data["representation"]], - "family": [self.data["family"]] - } - - else: - asset_regex = re.compile(self.data["asset"]) - linked_asset_names = [] - for asset_doc in linked_asset_docs: - asset_name = asset_doc["name"] - if asset_regex.match(asset_name): - linked_asset_names.append(asset_name) - - if not linked_asset_names: - return [] - - context_filters = { - "asset": linked_asset_names, - "subset": [re.compile(self.data["subset"])], - "hierarchy": [re.compile(self.data["hierarchy"])], - "representation": [self.data["representation"]], - "family": [self.data["family"]], - } - - return list(get_representations( - project_name, - context_filters=context_filters - )) - - def err_message(self): - return ( - "Error while trying to load a representation.\n" - "Either the subset wasn't published or the template is malformed." - "\n\n" - "Builder was looking for:\n{attributes}".format( - attributes="\n".join([ - "{}: {}".format(key.title(), value) - for key, value in self.data.items()] - ) - ) - ) From 8259a4129bc1439443ee7fa5d778e76f32ccd9df Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 16:22:16 +0200 Subject: [PATCH 340/513] removed functionality of previous template build logic --- .../workfile/abstract_template_loader.py | 528 ------------------ openpype/pipeline/workfile/build_template.py | 72 --- .../workfile/build_template_exceptions.py | 35 -- 3 files changed, 635 deletions(-) delete mode 100644 openpype/pipeline/workfile/abstract_template_loader.py delete mode 100644 openpype/pipeline/workfile/build_template.py delete mode 100644 openpype/pipeline/workfile/build_template_exceptions.py diff --git a/openpype/pipeline/workfile/abstract_template_loader.py b/openpype/pipeline/workfile/abstract_template_loader.py deleted file mode 100644 index e2fbea98ca..0000000000 --- a/openpype/pipeline/workfile/abstract_template_loader.py +++ /dev/null @@ -1,528 +0,0 @@ -import os -from abc import ABCMeta, abstractmethod - -import six -import logging -from functools import reduce - -from openpype.client import ( - get_asset_by_name, - get_linked_assets, -) -from openpype.settings import get_project_settings -from openpype.lib import ( - StringTemplate, - Logger, - filter_profiles, -) -from openpype.pipeline import legacy_io, Anatomy -from openpype.pipeline.load import ( - get_loaders_by_name, - get_representation_context, - load_with_repre_context, -) - -from .build_template_exceptions import ( - TemplateAlreadyImported, - TemplateLoadingFailed, - TemplateProfileNotFound, - TemplateNotFound -) - -log = logging.getLogger(__name__) - - -def update_representations(entities, entity): - if entity['context']['subset'] not in entities: - entities[entity['context']['subset']] = entity - else: - current = entities[entity['context']['subset']] - incomming = entity - entities[entity['context']['subset']] = max( - current, incomming, - key=lambda entity: entity["context"].get("version", -1)) - - return entities - - -def parse_loader_args(loader_args): - if not loader_args: - return dict() - try: - parsed_args = eval(loader_args) - if not isinstance(parsed_args, dict): - return dict() - else: - return parsed_args - except Exception as err: - print( - "Error while parsing loader arguments '{}'.\n{}: {}\n\n" - "Continuing with default arguments. . .".format( - loader_args, - err.__class__.__name__, - err)) - return dict() - - -@six.add_metaclass(ABCMeta) -class AbstractTemplateLoader: - """ - Abstraction of Template Loader. - Properties: - template_path : property to get current template path - Methods: - import_template : Abstract Method. Used to load template, - depending on current host - get_template_nodes : Abstract Method. Used to query nodes acting - as placeholders. Depending on current host - """ - - _log = None - - def __init__(self, placeholder_class): - # TODO template loader should expect host as and argument - # - host have all responsibility for most of code (also provide - # placeholder class) - # - also have responsibility for current context - # - this won't work in DCCs where multiple workfiles with - # different contexts can be opened at single time - # - template loader should have ability to change context - project_name = legacy_io.active_project() - asset_name = legacy_io.Session["AVALON_ASSET"] - - self.loaders_by_name = get_loaders_by_name() - self.current_asset = asset_name - self.project_name = project_name - self.host_name = legacy_io.Session["AVALON_APP"] - self.task_name = legacy_io.Session["AVALON_TASK"] - self.placeholder_class = placeholder_class - self.current_asset_doc = get_asset_by_name(project_name, asset_name) - self.task_type = ( - self.current_asset_doc - .get("data", {}) - .get("tasks", {}) - .get(self.task_name, {}) - .get("type") - ) - - self.log.info( - "BUILDING ASSET FROM TEMPLATE :\n" - "Starting templated build for {asset} in {project}\n\n" - "Asset : {asset}\n" - "Task : {task_name} ({task_type})\n" - "Host : {host}\n" - "Project : {project}\n".format( - asset=self.current_asset, - host=self.host_name, - project=self.project_name, - task_name=self.task_name, - task_type=self.task_type - )) - # Skip if there is no loader - if not self.loaders_by_name: - self.log.warning( - "There is no registered loaders. No assets will be loaded") - return - - @property - def log(self): - if self._log is None: - self._log = Logger.get_logger(self.__class__.__name__) - return self._log - - def template_already_imported(self, err_msg): - """In case template was already loaded. - Raise the error as a default action. - Override this method in your template loader implementation - to manage this case.""" - self.log.error("{}: {}".format( - err_msg.__class__.__name__, - err_msg)) - raise TemplateAlreadyImported(err_msg) - - def template_loading_failed(self, err_msg): - """In case template loading failed - Raise the error as a default action. - Override this method in your template loader implementation - to manage this case. - """ - self.log.error("{}: {}".format( - err_msg.__class__.__name__, - err_msg)) - raise TemplateLoadingFailed(err_msg) - - @property - def template_path(self): - """ - Property returning template path. Avoiding setter. - Getting template path from open pype settings based on current avalon - session and solving the path variables if needed. - Returns: - str: Solved template path - Raises: - TemplateProfileNotFound: No profile found from settings for - current avalon session - KeyError: Could not solve path because a key does not exists - in avalon context - TemplateNotFound: Solved path does not exists on current filesystem - """ - project_name = self.project_name - host_name = self.host_name - task_name = self.task_name - task_type = self.task_type - - anatomy = Anatomy(project_name) - project_settings = get_project_settings(project_name) - - build_info = project_settings[host_name]["templated_workfile_build"] - profile = filter_profiles( - build_info["profiles"], - { - "task_types": task_type, - "task_names": task_name - } - ) - - if not profile: - raise TemplateProfileNotFound( - "No matching profile found for task '{}' of type '{}' " - "with host '{}'".format(task_name, task_type, host_name) - ) - - path = profile["path"] - if not path: - raise TemplateLoadingFailed( - "Template path is not set.\n" - "Path need to be set in {}\\Template Workfile Build " - "Settings\\Profiles".format(host_name.title())) - - # Try fill path with environments and anatomy roots - fill_data = { - key: value - for key, value in os.environ.items() - } - fill_data["root"] = anatomy.roots - result = StringTemplate.format_template(path, fill_data) - if result.solved: - path = result.normalized() - - if path and os.path.exists(path): - self.log.info("Found template at: '{}'".format(path)) - return path - - solved_path = None - while True: - try: - solved_path = anatomy.path_remapper(path) - except KeyError as missing_key: - raise KeyError( - "Could not solve key '{}' in template path '{}'".format( - missing_key, path)) - - if solved_path is None: - solved_path = path - if solved_path == path: - break - path = solved_path - - solved_path = os.path.normpath(solved_path) - if not os.path.exists(solved_path): - raise TemplateNotFound( - "Template found in openPype settings for task '{}' with host " - "'{}' does not exists. (Not found : {})".format( - task_name, host_name, solved_path)) - - self.log.info("Found template at: '{}'".format(solved_path)) - - return solved_path - - def populate_template(self, ignored_ids=None): - """ - Use template placeholders to load assets and parent them in hierarchy - Arguments : - ignored_ids : - Returns: - None - """ - - loaders_by_name = self.loaders_by_name - current_asset_doc = self.current_asset_doc - linked_assets = get_linked_assets(current_asset_doc) - - ignored_ids = ignored_ids or [] - placeholders = self.get_placeholders() - self.log.debug("Placeholders found in template: {}".format( - [placeholder.name for placeholder in placeholders] - )) - for placeholder in placeholders: - self.log.debug("Start to processing placeholder {}".format( - placeholder.name - )) - placeholder_representations = self.get_placeholder_representations( - placeholder, - current_asset_doc, - linked_assets - ) - - if not placeholder_representations: - self.log.info( - "There's no representation for this placeholder: " - "{}".format(placeholder.name) - ) - continue - - for representation in placeholder_representations: - self.preload(placeholder, loaders_by_name, representation) - - if self.load_data_is_incorrect( - placeholder, - representation, - ignored_ids): - continue - - self.log.info( - "Loading {}_{} with loader {}\n" - "Loader arguments used : {}".format( - representation['context']['asset'], - representation['context']['subset'], - placeholder.loader_name, - placeholder.loader_args)) - - try: - container = self.load( - placeholder, loaders_by_name, representation) - except Exception: - self.load_failed(placeholder, representation) - else: - self.load_succeed(placeholder, container) - finally: - self.postload(placeholder) - - def get_placeholder_representations( - self, placeholder, current_asset_doc, linked_asset_docs - ): - placeholder_representations = placeholder.get_representations( - current_asset_doc, - linked_asset_docs - ) - for repre_doc in reduce( - update_representations, - placeholder_representations, - dict() - ).values(): - yield repre_doc - - def load_data_is_incorrect( - self, placeholder, last_representation, ignored_ids): - if not last_representation: - self.log.warning(placeholder.err_message()) - return True - if (str(last_representation['_id']) in ignored_ids): - print("Ignoring : ", last_representation['_id']) - return True - return False - - def preload(self, placeholder, loaders_by_name, last_representation): - pass - - def load(self, placeholder, loaders_by_name, last_representation): - repre = get_representation_context(last_representation) - return load_with_repre_context( - loaders_by_name[placeholder.loader_name], - repre, - options=parse_loader_args(placeholder.loader_args)) - - def load_succeed(self, placeholder, container): - placeholder.parent_in_hierarchy(container) - - def load_failed(self, placeholder, last_representation): - self.log.warning( - "Got error trying to load {}:{} with {}".format( - last_representation['context']['asset'], - last_representation['context']['subset'], - placeholder.loader_name - ), - exc_info=True - ) - - def postload(self, placeholder): - placeholder.clean() - - def update_missing_containers(self): - loaded_containers_ids = self.get_loaded_containers_by_id() - self.populate_template(ignored_ids=loaded_containers_ids) - - def get_placeholders(self): - placeholders = map(self.placeholder_class, self.get_template_nodes()) - valid_placeholders = filter( - lambda i: i.is_valid, - placeholders - ) - sorted_placeholders = list(sorted( - valid_placeholders, - key=lambda i: i.order - )) - return sorted_placeholders - - @abstractmethod - def get_loaded_containers_by_id(self): - """ - Collect already loaded containers for updating scene - Return: - dict (string, node): A dictionnary id as key - and containers as value - """ - pass - - @abstractmethod - def import_template(self, template_path): - """ - Import template in current host - Args: - template_path (str): fullpath to current task and - host's template file - Return: - None - """ - pass - - @abstractmethod - def get_template_nodes(self): - """ - Returning a list of nodes acting as host placeholders for - templating. The data representation is by user. - AbstractLoadTemplate (and LoadTemplate) won't directly manipulate nodes - Args : - None - Returns: - list(AnyNode): Solved template path - """ - pass - - -@six.add_metaclass(ABCMeta) -class AbstractPlaceholder: - """Abstraction of placeholders logic. - - Properties: - required_keys: A list of mandatory keys to decribe placeholder - and assets to load. - optional_keys: A list of optional keys to decribe - placeholder and assets to load - loader_name: Name of linked loader to use while loading assets - - Args: - identifier (str): Placeholder identifier. Should be possible to be - used as identifier in "a scene" (e.g. unique node name). - """ - - required_keys = { - "builder_type", - "family", - "representation", - "order", - "loader", - "loader_args" - } - optional_keys = {} - - def __init__(self, identifier): - self._log = None - self._name = identifier - self.get_data(identifier) - - @property - def log(self): - if self._log is None: - self._log = Logger.get_logger(repr(self)) - return self._log - - def __repr__(self): - return "< {} {} >".format(self.__class__.__name__, self.name) - - @property - def name(self): - return self._name - - @property - def loader_args(self): - return self.data["loader_args"] - - @property - def builder_type(self): - return self.data["builder_type"] - - @property - def order(self): - return self.data["order"] - - @property - def loader_name(self): - """Return placeholder loader name. - - Returns: - str: Loader name that will be used to load placeholder - representations. - """ - - return self.data["loader"] - - @property - def is_valid(self): - """Test validity of placeholder. - - i.e.: every required key exists in placeholder data - - Returns: - bool: True if every key is in data - """ - - if set(self.required_keys).issubset(self.data.keys()): - self.log.debug("Valid placeholder : {}".format(self.name)) - return True - self.log.info("Placeholder is not valid : {}".format(self.name)) - return False - - @abstractmethod - def parent_in_hierarchy(self, container): - """Place loaded container in correct hierarchy given by placeholder - - Args: - container (Dict[str, Any]): Loaded container created by loader. - """ - - pass - - @abstractmethod - def clean(self): - """Clean placeholder from hierarchy after loading assets.""" - - pass - - @abstractmethod - def get_representations(self, current_asset_doc, linked_asset_docs): - """Query representations based on placeholder data. - - Args: - current_asset_doc (Dict[str, Any]): Document of current - context asset. - linked_asset_docs (List[Dict[str, Any]]): Documents of assets - linked to current context asset. - - Returns: - Iterable[Dict[str, Any]]: Representations that are matching - placeholder filters. - """ - - pass - - @abstractmethod - def get_data(self, identifier): - """Collect information about placeholder by identifier. - - Args: - identifier (str): A unique placeholder identifier defined by - implementation. - """ - - pass diff --git a/openpype/pipeline/workfile/build_template.py b/openpype/pipeline/workfile/build_template.py deleted file mode 100644 index 3328dfbc9e..0000000000 --- a/openpype/pipeline/workfile/build_template.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -from importlib import import_module -from openpype.lib import classes_from_module -from openpype.host import HostBase -from openpype.pipeline import registered_host - -from .abstract_template_loader import ( - AbstractPlaceholder, - AbstractTemplateLoader) - -from .build_template_exceptions import ( - TemplateLoadingFailed, - TemplateAlreadyImported, - MissingHostTemplateModule, - MissingTemplatePlaceholderClass, - MissingTemplateLoaderClass -) - -_module_path_format = 'openpype.hosts.{host}.api.template_loader' - - -def build_workfile_template(*args): - template_loader = build_template_loader() - try: - template_loader.import_template(template_loader.template_path) - except TemplateAlreadyImported as err: - template_loader.template_already_imported(err) - except TemplateLoadingFailed as err: - template_loader.template_loading_failed(err) - else: - template_loader.populate_template() - - -def update_workfile_template(*args): - template_loader = build_template_loader() - template_loader.update_missing_containers() - - -def build_template_loader(): - # TODO refactor to use advantage of 'HostBase' and don't import dynamically - # - hosts should have methods that gives option to return builders - host = registered_host() - if isinstance(host, HostBase): - host_name = host.name - else: - host_name = os.environ.get("AVALON_APP") - if not host_name: - host_name = host.__name__.split(".")[-2] - - module_path = _module_path_format.format(host=host_name) - module = import_module(module_path) - if not module: - raise MissingHostTemplateModule( - "No template loader found for host {}".format(host_name)) - - template_loader_class = classes_from_module( - AbstractTemplateLoader, - module - ) - template_placeholder_class = classes_from_module( - AbstractPlaceholder, - module - ) - - if not template_loader_class: - raise MissingTemplateLoaderClass() - template_loader_class = template_loader_class[0] - - if not template_placeholder_class: - raise MissingTemplatePlaceholderClass() - template_placeholder_class = template_placeholder_class[0] - return template_loader_class(template_placeholder_class) diff --git a/openpype/pipeline/workfile/build_template_exceptions.py b/openpype/pipeline/workfile/build_template_exceptions.py deleted file mode 100644 index 7a5075e3dc..0000000000 --- a/openpype/pipeline/workfile/build_template_exceptions.py +++ /dev/null @@ -1,35 +0,0 @@ -class MissingHostTemplateModule(Exception): - """Error raised when expected module does not exists""" - pass - - -class MissingTemplatePlaceholderClass(Exception): - """Error raised when module doesn't implement a placeholder class""" - pass - - -class MissingTemplateLoaderClass(Exception): - """Error raised when module doesn't implement a template loader class""" - pass - - -class TemplateNotFound(Exception): - """Exception raised when template does not exist.""" - pass - - -class TemplateProfileNotFound(Exception): - """Exception raised when current profile - doesn't match any template profile""" - pass - - -class TemplateAlreadyImported(Exception): - """Error raised when Template was already imported by host for - this session""" - pass - - -class TemplateLoadingFailed(Exception): - """Error raised whend Template loader was unable to load the template""" - pass From 1da23985a9e5e8845933d9eb8d239c39aca0f1ac Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 17:16:07 +0200 Subject: [PATCH 341/513] unified LoadPlaceholderItem --- .../maya/api/workfile_template_builder.py | 40 +++++-------------- .../nuke/api/workfile_template_builder.py | 29 +------------- .../workfile/workfile_template_builder.py | 40 +++++++++++++++++-- 3 files changed, 47 insertions(+), 62 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 71e3e0ce4e..9163cf9a6f 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -7,7 +7,7 @@ from openpype.pipeline.workfile.workfile_template_builder import ( TemplateAlreadyImported, AbstractTemplateBuilder, PlaceholderPlugin, - PlaceholderItem, + LoadPlaceholderItem, PlaceholderLoadMixin, ) from openpype.tools.workfile_template_build import ( @@ -239,15 +239,10 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): cmds.hide(node) cmds.setAttr(node + ".hiddenInOutliner", True) + def load_succeed(self, placeholder, container): + self._parent_in_hierarhchy(placeholder, container) -class LoadPlaceholderItem(PlaceholderItem): - """Concrete implementation of PlaceholderItem for Maya load plugin.""" - - def __init__(self, *args, **kwargs): - super(LoadPlaceholderItem, self).__init__(*args, **kwargs) - self._failed_representations = [] - - def parent_in_hierarchy(self, container): + def _parent_in_hierarchy(self, placeholder, container): """Parent loaded container to placeholder's parent. ie : Set loaded content as placeholder's sibling @@ -272,43 +267,26 @@ class LoadPlaceholderItem(PlaceholderItem): elif not cmds.sets(root, q=True): return - if self.data["parent"]: - cmds.parent(nodes_to_parent, self.data["parent"]) + if placeholder.data["parent"]: + cmds.parent(nodes_to_parent, placeholder.data["parent"]) # Move loaded nodes to correct index in outliner hierarchy placeholder_form = cmds.xform( - self._scene_identifier, + placeholder.scene_identifier, q=True, matrix=True, worldSpace=True ) for node in set(nodes_to_parent): cmds.reorder(node, front=True) - cmds.reorder(node, relative=self.data["index"]) + cmds.reorder(node, relative=placeholder.data["index"]) cmds.xform(node, matrix=placeholder_form, ws=True) - holding_sets = cmds.listSets(object=self._scene_identifier) + holding_sets = cmds.listSets(object=placeholder.scene_identifier) if not holding_sets: return for holding_set in holding_sets: cmds.sets(roots, forceElement=holding_set) - def get_errors(self): - if not self._failed_representations: - return [] - message = ( - "Failed to load {} representations using Loader {}" - ).format( - len(self._failed_representations), - self.data["loader"] - ) - return [message] - - def load_failed(self, representation): - self._failed_representations.append(representation) - - def load_succeed(self, container): - self.parent_in_hierarchy(container) - def build_workfile_template(*args): builder = MayaTemplateBuilder(registered_host()) diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index ba0d975496..709ee3b743 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -6,7 +6,7 @@ from openpype.pipeline import registered_host from openpype.pipeline.workfile.workfile_template_builder import ( AbstractTemplateBuilder, PlaceholderPlugin, - PlaceholderItem, + LoadPlaceholderItem, PlaceholderLoadMixin, ) from openpype.tools.workfile_template_build import ( @@ -177,7 +177,7 @@ class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): placeholder_data = self._parse_placeholder_node_data(node) # TODO do data validations and maybe updgrades if are invalid output.append( - NukeLoadPlaceholderItem(node_name, placeholder_data, self) + LoadPlaceholderItem(node_name, placeholder_data, self) ) return output @@ -535,31 +535,6 @@ class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): siblings_input.setInput(0, copy_output) -class NukeLoadPlaceholderItem(PlaceholderItem): - """Concrete implementation of PlaceholderItem for Maya load plugin.""" - - def __init__(self, *args, **kwargs): - super(NukeLoadPlaceholderItem, self).__init__(*args, **kwargs) - self._failed_representations = [] - - def get_errors(self): - if not self._failed_representations: - return [] - message = ( - "Failed to load {} representations using Loader {}" - ).format( - len(self._failed_representations), - self.data["loader"] - ) - return [message] - - def load_failed(self, representation): - self._failed_representations.append(representation) - - def load_succeed(self, container): - pass - - def build_workfile_template(*args): builder = NukeTemplateBuilder(registered_host()) builder.build_template() diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index f81849fbe4..582657c735 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -1064,10 +1064,9 @@ class PlaceholderLoadMixin(object): For placeholder population is implemented 'populate_load_placeholder'. - Requires that PlaceholderItem has implemented methods: + PlaceholderItem can have implemented methods: - 'load_failed' - called when loading of one representation failed - 'load_succeed' - called when loading of one representation succeeded - - 'clean' - called when placeholder processing finished """ def get_load_plugin_options(self, options=None): @@ -1397,13 +1396,21 @@ class PlaceholderLoadMixin(object): except Exception: failed = True - placeholder.load_failed(representation) + self.load_failed(placeholder, representation) else: failed = False - placeholder.load_succeed(container) + self.load_succeed(placeholder, container) self.cleanup_placeholder(placeholder, failed) + def load_failed(self, placeholder, representation): + if hasattr(placeholder, "load_failed"): + placeholder.load_failed(representation) + + def load_succeed(self, placeholder, container): + if hasattr(placeholder, "load_succeed"): + placeholder.load_succeed(container) + def cleanup_placeholder(self, placeholder, failed): """Cleanup placeholder after load of single representation. @@ -1417,3 +1424,28 @@ class PlaceholderLoadMixin(object): """ pass + + +class LoadPlaceholderItem(PlaceholderItem): + """PlaceholderItem for plugin which is loading representations. + + Connected to 'PlaceholderLoadMixin'. + """ + + def __init__(self, *args, **kwargs): + super(LoadPlaceholderItem, self).__init__(*args, **kwargs) + self._failed_representations = [] + + def get_errors(self): + if not self._failed_representations: + return [] + message = ( + "Failed to load {} representations using Loader {}" + ).format( + len(self._failed_representations), + self.data["loader"] + ) + return [message] + + def load_failed(self, representation): + self._failed_representations.append(representation) From b151c04b00305c837f29dd4820c7e8a99c1a66b5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 17:45:40 +0200 Subject: [PATCH 342/513] removed unused variables --- openpype/tools/workfile_template_build/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/workfile_template_build/window.py b/openpype/tools/workfile_template_build/window.py index 2e531026cf..757ccc0b4a 100644 --- a/openpype/tools/workfile_template_build/window.py +++ b/openpype/tools/workfile_template_build/window.py @@ -205,7 +205,7 @@ class WorkfileBuildPlaceholderDialog(QtWidgets.QDialog): try: plugin.update_placeholder(self._update_item, options) self.accept() - except Exception as exc: + except Exception: self.log.warning("Something went wrong", exc_info=True) dialog = QtWidgets.QMessageBox(self) dialog.setWindowTitle("Something went wrong") @@ -221,7 +221,7 @@ class WorkfileBuildPlaceholderDialog(QtWidgets.QDialog): try: plugin.create_placeholder(options) self.accept() - except Exception as exc: + except Exception: self.log.warning("Something went wrong", exc_info=True) dialog = QtWidgets.QMessageBox(self) dialog.setWindowTitle("Something went wrong") From b5682af9ac880cc91adad91b66720f15d47e3463 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 17:46:27 +0200 Subject: [PATCH 343/513] fix variable usage --- openpype/tools/workfile_template_build/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/workfile_template_build/window.py b/openpype/tools/workfile_template_build/window.py index 757ccc0b4a..ea4e2fec5a 100644 --- a/openpype/tools/workfile_template_build/window.py +++ b/openpype/tools/workfile_template_build/window.py @@ -126,8 +126,8 @@ class WorkfileBuildPlaceholderDialog(QtWidgets.QDialog): self._last_selected_plugin = None self._plugins_combo.clear() for identifier, plugin in placeholder_plugins.items(): - label = plugin.label or plugin.identifier - self._plugins_combo.addItem(label, plugin.identifier) + label = plugin.label or identifier + self._plugins_combo.addItem(label, identifier) index = self._plugins_combo.findData(last_selected_plugin) if index < 0: From b3bb5f8612ccb623f5da34e4270e8db9bcdb8a7e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 23:09:17 +0200 Subject: [PATCH 344/513] Always increment current file - similar to Maya --- .../plugins/publish/increment_current_file.py | 29 +++------------ .../increment_current_file_deadline.py | 35 ------------------- 2 files changed, 4 insertions(+), 60 deletions(-) delete mode 100644 openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py index 5cb14d732a..c990f481d3 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py @@ -2,10 +2,9 @@ import pyblish.api from openpype.lib import version_up from openpype.pipeline import registered_host -from openpype.pipeline.publish import get_errored_plugins_from_context -class IncrementCurrentFile(pyblish.api.InstancePlugin): +class IncrementCurrentFile(pyblish.api.ContextPlugin): """Increment the current file. Saves the current scene with an increased version number. @@ -15,30 +14,10 @@ class IncrementCurrentFile(pyblish.api.InstancePlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["houdini"] - families = ["colorbleed.usdrender", "redshift_rop"] - targets = ["local"] + families = ["workfile"] + optional = True - def process(self, instance): - - # This should be a ContextPlugin, but this is a workaround - # for a bug in pyblish to run once for a family: issue #250 - context = instance.context - key = "__hasRun{}".format(self.__class__.__name__) - if context.data.get(key, False): - return - else: - context.data[key] = True - - context = instance.context - errored_plugins = get_errored_plugins_from_context(context) - if any( - plugin.__name__ == "HoudiniSubmitPublishDeadline" - for plugin in errored_plugins - ): - raise RuntimeError( - "Skipping incrementing current file because " - "submission to deadline failed." - ) + def process(self, context): # Filename must not have changed since collecting host = registered_host() diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py b/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py deleted file mode 100644 index cb0d7e3680..0000000000 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py +++ /dev/null @@ -1,35 +0,0 @@ -import pyblish.api - -import hou -from openpype.lib import version_up -from openpype.pipeline.publish import get_errored_plugins_from_context - - -class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): - """Increment the current file. - - Saves the current scene with an increased version number. - - """ - - label = "Increment current file" - order = pyblish.api.IntegratorOrder + 9.0 - hosts = ["houdini"] - targets = ["deadline"] - - def process(self, context): - - errored_plugins = get_errored_plugins_from_context(context) - if any( - plugin.__name__ == "HoudiniSubmitPublishDeadline" - for plugin in errored_plugins - ): - raise RuntimeError( - "Skipping incrementing current file because " - "submission to deadline failed." - ) - - current_filepath = context.data["currentFile"] - new_filepath = version_up(current_filepath) - - hou.hipFile.save(file_name=new_filepath, save_to_recent_files=True) From 229d31bc1ca10d51ab2b562ed128623a8895d26b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 23:41:42 +0200 Subject: [PATCH 345/513] Collect global in/out as handles --- .../fusion/plugins/publish/collect_instances.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index b2192d1dd9..b36e43cacd 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -4,19 +4,21 @@ import pyblish.api def get_comp_render_range(comp): - """Return comp's start and end render range.""" + """Return comp's start-end render range and global start-end range.""" comp_attrs = comp.GetAttrs() start = comp_attrs["COMPN_RenderStart"] end = comp_attrs["COMPN_RenderEnd"] + global_start = comp_attrs["COMPN_GlobalStart"] + global_end = comp_attrs["COMPN_GlobalEnd"] # Whenever render ranges are undefined fall back # to the comp's global start and end if start == -1000000000: - start = comp_attrs["COMPN_GlobalEnd"] + start = global_start if end == -1000000000: - end = comp_attrs["COMPN_GlobalStart"] + end = global_end - return start, end + return start, end, global_start, global_end class CollectInstances(pyblish.api.ContextPlugin): @@ -42,9 +44,11 @@ class CollectInstances(pyblish.api.ContextPlugin): tools = comp.GetToolList(False).values() savers = [tool for tool in tools if tool.ID == "Saver"] - start, end = get_comp_render_range(comp) + start, end, global_start, global_end = get_comp_render_range(comp) context.data["frameStart"] = int(start) context.data["frameEnd"] = int(end) + context.data["frameStartHandle"] = int(global_start) + context.data["frameEndHandle"] = int(global_end) for tool in savers: path = tool["Clip"][comp.TIME_UNDEFINED] @@ -78,6 +82,8 @@ class CollectInstances(pyblish.api.ContextPlugin): "label": label, "frameStart": context.data["frameStart"], "frameEnd": context.data["frameEnd"], + "frameStartHandle": context.data["frameStartHandle"], + "frameEndHandle": context.data["frameStartHandle"], "fps": context.data["fps"], "families": ["render", "review", "ftrack"], "family": "render", From 8d4d80c2258f2e6a3a7c799547b9940af74cfdb8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 23:56:48 +0200 Subject: [PATCH 346/513] Be more explicit about the to render frame range (include rendering of handles) --- .../hosts/fusion/plugins/publish/render_local.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py index 601c2ffccf..79e458b40a 100644 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ b/openpype/hosts/fusion/plugins/publish/render_local.py @@ -20,6 +20,8 @@ class Fusionlocal(pyblish.api.InstancePlugin): def process(self, instance): + # This plug-in runs only once and thus assumes all instances + # currently will render the same frame range context = instance.context key = "__hasRun{}".format(self.__class__.__name__) if context.data.get(key, False): @@ -28,8 +30,8 @@ class Fusionlocal(pyblish.api.InstancePlugin): context.data[key] = True current_comp = context.data["currentComp"] - frame_start = current_comp.GetAttrs("COMPN_RenderStart") - frame_end = current_comp.GetAttrs("COMPN_RenderEnd") + frame_start = context.data["frameStartHandle"] + frame_end = context.data["frameEndHandle"] path = instance.data["path"] output_dir = instance.data["outputDir"] @@ -40,7 +42,11 @@ class Fusionlocal(pyblish.api.InstancePlugin): self.log.info("End frame: {}".format(frame_end)) with comp_lock_and_undo_chunk(current_comp): - result = current_comp.Render() + result = current_comp.Render({ + "Start": frame_start, + "End": frame_end, + "Wait": True + }) if "representations" not in instance.data: instance.data["representations"] = [] From e12de9b3b2bfd2d28cc8cbeb620b01babca54e6d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 00:02:14 +0200 Subject: [PATCH 347/513] Do not auto-add ftrack family - That should be left up to plug-ins in Ftrack module --- openpype/hosts/fusion/plugins/publish/collect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index b36e43cacd..fe60b83827 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -85,7 +85,7 @@ class CollectInstances(pyblish.api.ContextPlugin): "frameStartHandle": context.data["frameStartHandle"], "frameEndHandle": context.data["frameStartHandle"], "fps": context.data["fps"], - "families": ["render", "review", "ftrack"], + "families": ["render", "review"], "family": "render", "active": active, "publish": active # backwards compatibility From 48546ef24f6c9b1db77e6094792ba2b2f1f88bc3 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 14 Sep 2022 04:18:24 +0000 Subject: [PATCH 348/513] [Automated] Bump version --- CHANGELOG.md | 32 ++++++++++++++++++-------------- openpype/version.py | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46bf56f5bd..7d6b620d58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,22 @@ # Changelog +## [3.14.3-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.2...HEAD) + +**🚀 Enhancements** + +- Publisher: Add new publisher to host tools [\#3833](https://github.com/pypeclub/OpenPype/pull/3833) +- Maya: Workspace mel loaded from settings [\#3790](https://github.com/pypeclub/OpenPype/pull/3790) + +**🐛 Bug fixes** + +- Ftrack: Url validation does not require ftrackapp [\#3834](https://github.com/pypeclub/OpenPype/pull/3834) +- Maya+Ftrack: Change typo in family name `mayaascii` -\> `mayaAscii` [\#3820](https://github.com/pypeclub/OpenPype/pull/3820) + ## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...3.14.2) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.2-nightly.5...3.14.2) **🆕 New features** @@ -45,11 +59,10 @@ - Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) - General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) -- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) - Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) - Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) -- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) - Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) **Merged pull requests:** @@ -72,7 +85,6 @@ - General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) - Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) - Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) -- General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) **🐛 Bug fixes** @@ -86,17 +98,17 @@ - Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) - Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) - PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) -- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) **🔀 Refactored code** - General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) -- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) +- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) - AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) +- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) - AfterEffects: Define AfterEffects as module [\#3728](https://github.com/pypeclub/OpenPype/pull/3728) - General: Replace PypeLogger with Logger [\#3725](https://github.com/pypeclub/OpenPype/pull/3725) - Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) @@ -118,14 +130,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.0-nightly.1...3.14.0) -**🚀 Enhancements** - -- Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) - -**🐛 Bug fixes** - -- General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) - ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.13.0-nightly.1...3.13.0) diff --git a/openpype/version.py b/openpype/version.py index 8469b1712a..e8a65b04d2 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2" +__version__ = "3.14.3-nightly.1" From d6b7e666e8fa5342c69a0ed027774c4bc3804e28 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 14 Sep 2022 12:37:30 +0800 Subject: [PATCH 349/513] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 3b84d91158..35d0026357 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -493,7 +493,7 @@ def check_lock_on_current_file(): # add lockfile dialog from Qt import QtWidgets top_level_widgets = {w.objectName(): w for w in - QtWidgets.QApplication.topLevelWidgets()} + QtWidgets.QApplication.topLevelWidgets()} parent = top_level_widgets.get("MayaWindow", None) workfile_dialog = WorkfileLockDialog(filepath, parent=parent) if not workfile_dialog.exec_(): @@ -502,6 +502,7 @@ def check_lock_on_current_file(): create_workfile_lock(filepath) + def on_before_close(): """Delete the lock file after user quitting the Maya Scene""" log.info("Closing Maya...") From b1ebef457c23a3e48e077cd63040e7b15b9828a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 14 Sep 2022 11:36:10 +0200 Subject: [PATCH 350/513] :sparkles: add script for python dependencies info --- tools/get_python_packages_info.py | 83 +++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tools/get_python_packages_info.py diff --git a/tools/get_python_packages_info.py b/tools/get_python_packages_info.py new file mode 100644 index 0000000000..b4952840e6 --- /dev/null +++ b/tools/get_python_packages_info.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +"""Get version and license information on used Python packages. + +This is getting over all packages installed with Poetry and printing out +their name, version and available license information from PyPi in Markdown +table format. + +Usage: + ./.poetry/bin/poetry run python ./tools/get_python_packages_info.py + +""" + +import toml +import requests + + +packages = [] + +# define column headers +package_header = "Package" +version_header = "Version" +license_header = "License" + +name_col_width = len(package_header) +version_col_width = len(version_header) +license_col_width = len(license_header) + +# read lock file to get packages +with open("poetry.lock", "r") as fb: + lock_content = toml.load(fb) + + for package in lock_content["package"]: + # query pypi for license information + url = f"https://pypi.org/pypi/{package['name']}/json" + response = requests.get( + f"https://pypi.org/pypi/{package['name']}/json") + package_data = response.json() + version = package.get("version") or "N/A" + try: + package_license = package_data["info"].get("license") or "N/A" + except KeyError: + package_license = "N/A" + + if len(package_license) > 64: + package_license = f"{package_license[:32]}..." + packages.append( + ( + package["name"], + version, + package_license + ) + ) + + # update column width based on max string length + if len(package["name"]) > name_col_width: + name_col_width = len(package["name"]) + if len(version) > version_col_width: + version_col_width = len(version) + if len(package_license) > license_col_width: + license_col_width = len(package_license) + +# pad columns +name_col_width += 2 +version_col_width += 2 +license_col_width += 2 + +# print table header +print((f"|{package_header.center(name_col_width)}" + f"|{version_header.center(version_col_width)}" + f"|{license_header.center(license_col_width)}|")) + +print( + "|" + ("-" * len(package_header.center(name_col_width))) + + "|" + ("-" * len(version_header.center(version_col_width))) + + "|" + ("-" * len(license_header.center(license_col_width))) + "|") + +# print rest of the table +for package in packages: + print(( + f"|{package[0].center(name_col_width)}" + f"|{package[1].center(version_col_width)}" + f"|{package[2].center(license_col_width)}|" + )) From d5cb828edc611f9ed9f98b61ae5ae3591d797640 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 11:45:44 +0200 Subject: [PATCH 351/513] Move LoadedInSceneDelegate into Loader tool since it's specific to loader --- openpype/tools/loader/delegates.py | 28 ++++++++++++++++++++++++++++ openpype/tools/loader/widgets.py | 4 ++-- openpype/tools/utils/delegates.py | 27 --------------------------- 3 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 openpype/tools/loader/delegates.py diff --git a/openpype/tools/loader/delegates.py b/openpype/tools/loader/delegates.py new file mode 100644 index 0000000000..e6663d48f1 --- /dev/null +++ b/openpype/tools/loader/delegates.py @@ -0,0 +1,28 @@ +from Qt import QtWidgets, QtGui, QtCore + + +class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate): + """Delegate for Loaded in Scene state columns. + + Shows "yes" or "no" for True or False values + Colorizes green or dark grey based on True or False values + + """ + + def __init__(self, *args, **kwargs): + super(LoadedInSceneDelegate, self).__init__(*args, **kwargs) + self._colors = { + True: QtGui.QColor(80, 170, 80), + False: QtGui.QColor(90, 90, 90) + } + + def displayText(self, value, locale): + return "yes" if value else "no" + + def initStyleOption(self, option, index): + super(LoadedInSceneDelegate, self).initStyleOption(option, index) + + # Colorize based on value + value = index.data(QtCore.Qt.DisplayRole) + color = self._colors[bool(value)] + option.palette.setBrush(QtGui.QPalette.Text, color) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 615f28f04d..98522c48ce 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -37,8 +37,7 @@ from openpype.tools.utils import ( ) from openpype.tools.utils.delegates import ( VersionDelegate, - PrettyTimeDelegate, - LoadedInSceneDelegate + PrettyTimeDelegate ) from openpype.tools.utils.widgets import ( OptionalMenu, @@ -59,6 +58,7 @@ from .model import ( ITEM_ID_ROLE ) from . import lib +from .delegates import LoadedInSceneDelegate from openpype.tools.utils.constants import ( LOCAL_PROVIDER_ROLE, diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 3547251282..d6c2d69e76 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -291,30 +291,3 @@ class PrettyTimeDelegate(QtWidgets.QStyledItemDelegate): def displayText(self, value, locale): if value is not None: return pretty_timestamp(value) - - -class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate): - """Delegate for Loaded in Scene state columns. - - Shows "yes" or "no" for True or False values - Colorizes green or dark grey based on True or False values - - """ - - def __init__(self, *args, **kwargs): - super(LoadedInSceneDelegate, self).__init__(*args, **kwargs) - self._colors = { - True: QtGui.QColor(80, 170, 80), - False: QtGui.QColor(90, 90, 90) - } - - def displayText(self, value, locale): - return "yes" if value else "no" - - def initStyleOption(self, option, index): - super(LoadedInSceneDelegate, self).initStyleOption(option, index) - - # Colorize based on value - value = index.data(QtCore.Qt.DisplayRole) - color = self._colors[bool(value)] - option.palette.setBrush(QtGui.QPalette.Text, color) From d84b175efbc60c226609c6393dddedbf9960c7be Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 11:47:52 +0200 Subject: [PATCH 352/513] Support ILoadHost hosts Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/loader/model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 58d6787507..17af7bb2f7 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -569,6 +569,11 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): if self._host: time_since_refresh = time.time() - self._host_loaded_refresh_time if time_since_refresh > self._host_loaded_refresh_timeout: + if isinstance(self._host, ILoadHost): + containers = self._host.get_containers() + else: + containers = self._host.ls() + repre_ids = {con.get("representation") for con in self._host.ls()} self._loaded_representation_ids = repre_ids From a8909889c4662764042288245ef2d901d1218055 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 11:49:52 +0200 Subject: [PATCH 353/513] Fix refactored code --- openpype/tools/loader/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 17af7bb2f7..1c078325f3 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -25,6 +25,7 @@ from openpype.pipeline import ( from openpype.style import get_default_entity_icon_color from openpype.tools.utils.models import TreeModel, Item from openpype.tools.utils import lib +from openpype.host import ILoadHost from openpype.modules import ModulesManager from openpype.tools.utils.constants import ( @@ -573,9 +574,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): containers = self._host.get_containers() else: containers = self._host.ls() - - repre_ids = {con.get("representation") - for con in self._host.ls()} + + repre_ids = {con.get("representation") for con in containers} self._loaded_representation_ids = repre_ids self._host_loaded_refresh_time = time.time() From 1eae84fd49af6930eb7865451c17557bf1d4d8b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 11:58:10 +0200 Subject: [PATCH 354/513] Switch columns --- openpype/tools/loader/model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 1c078325f3..77a8669c46 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -138,8 +138,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "duration", "handles", "step", - "repre_info", - "loaded_in_scene" + "loaded_in_scene", + "repre_info" ] column_labels_mapping = { @@ -153,8 +153,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "duration": "Duration", "handles": "Handles", "step": "Step", - "repre_info": "Availability", - "loaded_in_scene": "In scene" + "loaded_in_scene": "In scene", + "repre_info": "Availability" } SortAscendingRole = QtCore.Qt.UserRole + 2 From c3588e2f9d337109aea3c2630fb12079b617d81d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 12:37:46 +0200 Subject: [PATCH 355/513] Make `loaded_in_scene` column 5 pixels wider + reorder to match with column order --- openpype/tools/loader/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 98522c48ce..c028aa4174 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -170,8 +170,8 @@ class SubsetWidget(QtWidgets.QWidget): ("duration", 60), ("handles", 55), ("step", 10), - ("repre_info", 65), - ("loaded_in_scene", 20) + ("loaded_in_scene", 25), + ("repre_info", 65) ) def __init__( From 817886b234c00d6a9f2a9bd0902cf8a2a9cb9cb3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 13:39:10 +0200 Subject: [PATCH 356/513] Fix typo in logic --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 60d5d3ed4a..51e34312f2 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -154,7 +154,7 @@ def convert_value_by_type_name(value_type, value, logger=None): elif parts_len == 4: divisor = 2 elif parts_len == 9: - divisor == 3 + divisor = 3 elif parts_len == 16: divisor = 4 else: From 5c3c4dcbb2acfe736cfc94e6360eb2eb52cd580b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 13:43:43 +0200 Subject: [PATCH 357/513] Fix same typo in duplicated code --- .../plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 9fca1b5391..b259e18a94 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -71,7 +71,7 @@ def convert_value_by_type_name(value_type, value): elif parts_len == 4: divisor = 2 elif parts_len == 9: - divisor == 3 + divisor = 3 elif parts_len == 16: divisor = 4 else: From 6186c63c599822bddaf4fc2c2a437831f00e62b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 14 Sep 2022 13:50:01 +0200 Subject: [PATCH 358/513] added 'float2' type support --- openpype/lib/transcoding.py | 2 +- .../plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 60d5d3ed4a..71c12b3376 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -139,7 +139,7 @@ def convert_value_by_type_name(value_type, value, logger=None): return float(value) # Vectors will probably have more types - if value_type == "vec2f": + if value_type in ("vec2f", "float2"): return [float(item) for item in value.split(",")] # Matrix should be always have square size of element 3x3, 4x4 diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 05899de5e1..691c642e82 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -56,7 +56,7 @@ def convert_value_by_type_name(value_type, value): return float(value) # Vectors will probably have more types - if value_type == "vec2f": + if value_type in ("vec2f", "float2"): return [float(item) for item in value.split(",")] # Matrix should be always have square size of element 3x3, 4x4 From e5b82d112373905cc61e2030e3939a09eba90ee1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 14 Sep 2022 13:50:21 +0200 Subject: [PATCH 359/513] lowered log level and modified messages on unknown value type --- openpype/lib/transcoding.py | 8 ++++---- .../OpenPypeTileAssembler/OpenPypeTileAssembler.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 71c12b3376..5b919b4111 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -204,8 +204,8 @@ def convert_value_by_type_name(value_type, value, logger=None): ) return output - logger.info(( - "MISSING IMPLEMENTATION:" + logger.debug(( + "Dev note (missing implementation):" " Unknown attrib type \"{}\". Value: {}" ).format(value_type, value)) return value @@ -263,8 +263,8 @@ def parse_oiio_xml_output(xml_string, logger=None): # - feel free to add more tags else: value = child.text - logger.info(( - "MISSING IMPLEMENTATION:" + logger.debug(( + "Dev note (missing implementation):" " Unknown tag \"{}\". Value \"{}\"" ).format(tag_name, value)) diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 691c642e82..c5208590f2 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -127,7 +127,7 @@ def convert_value_by_type_name(value_type, value): return output print(( - "MISSING IMPLEMENTATION:" + "Dev note (missing implementation):" " Unknown attrib type \"{}\". Value: {}" ).format(value_type, value)) return value @@ -183,7 +183,7 @@ def parse_oiio_xml_output(xml_string): else: value = child.text print(( - "MISSING IMPLEMENTATION:" + "Dev note (missing implementation):" " Unknown tag \"{}\". Value \"{}\"" ).format(tag_name, value)) From 1c6b23b674eec3ed1fc1b1e0a68931a5661b71a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 13:50:57 +0200 Subject: [PATCH 360/513] Fix `headsUpDisplay` key name Capture has a default setting named `headsUpDisplay` which is the long name for the setting `hud`. Thus when supplying `hud` as viewport option then `capture` will merge the key-values and thus will try to set both `headsUpDisplay` and `hud` value for the modelEditor which ends up ignoring `hud` and instead applying the `headsUpDisplay`. Thus, `hud` didn't do anything. --- openpype/settings/defaults/project_settings/maya.json | 2 +- .../schemas/projects_schema/schemas/schema_maya_capture.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 99ba4cdd5c..7759ac4e5e 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -731,7 +731,7 @@ "grid": false, "hairSystems": true, "handles": false, - "hud": false, + "headsUpDisplay": false, "hulls": false, "ikHandles": false, "imagePlane": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 7a40f349cc..ab35fd391f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -441,8 +441,8 @@ }, { "type": "boolean", - "key": "hud", - "label": "hud" + "key": "headsUpDisplay", + "label": "headsUpDisplay" }, { "type": "boolean", From 9a19da923c783e253c2f249842ed5c1409d2a5c3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 14 Sep 2022 20:49:48 +0800 Subject: [PATCH 361/513] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 41 ++++++++++++++++++------ openpype/tools/workfiles/files_widget.py | 4 +-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 35d0026357..eb22eeeb3b 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -106,11 +106,13 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): register_event_callback("open", on_open) register_event_callback("new", on_new) register_event_callback("before.save", on_before_save) + register_event_callback("after.save", on_after_save) register_event_callback("before.close", on_before_close) register_event_callback("before.file.open", before_file_open) register_event_callback("taskChanged", on_task_changed) register_event_callback("workfile.open.before", before_workfile_open) register_event_callback("workfile.save.before", before_workfile_save) + register_event_callback("workfile.save.after", after_workfile_save) def open_workfile(self, filepath): return open_file(filepath) @@ -153,6 +155,10 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): OpenMaya.MSceneMessage.kBeforeSave, _on_scene_save ) + self._op_events[_after_scene_save] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterSave, _after_scene_save + ) + self._op_events[_before_scene_save] = ( OpenMaya.MSceneMessage.addCheckCallback( OpenMaya.MSceneMessage.kBeforeSaveCheck, @@ -194,6 +200,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): self.log.info("Installed event handler _on_scene_save..") self.log.info("Installed event handler _before_scene_save..") + self.log.info("Insatall event handler _on_after_save..") self.log.info("Installed event handler _on_scene_new..") self.log.info("Installed event handler _on_maya_initialized..") self.log.info("Installed event handler _on_scene_open..") @@ -236,6 +243,8 @@ def _on_maya_initialized(*args): def _on_scene_new(*args): emit_event("new") +def _after_scene_save(*arg): + emit_event("after.save") def _on_scene_save(*args): emit_event("save") @@ -271,6 +280,7 @@ def _remove_workfile_lock(): if not handle_workfile_locks(): return filepath = current_file() + log.info("Removing lock on current file {}...".format(filepath)) if filepath: remove_workfile_lock(filepath) @@ -479,6 +489,13 @@ def on_before_save(): return lib.validate_fps() +def on_after_save(): + """Check if there is a lockfile after save""" + filepath = current_file() + if not is_workfile_locked(filepath): + create_workfile_lock(filepath) + + def check_lock_on_current_file(): """Check if there is a user opening the file""" @@ -491,14 +508,14 @@ def check_lock_on_current_file(): if is_workfile_locked(filepath): # add lockfile dialog - from Qt import QtWidgets - top_level_widgets = {w.objectName(): w for w in - QtWidgets.QApplication.topLevelWidgets()} - parent = top_level_widgets.get("MayaWindow", None) - workfile_dialog = WorkfileLockDialog(filepath, parent=parent) - if not workfile_dialog.exec_(): - cmds.file(new=True) - return + try: + workfile_dialog.close() + workfile_dialog.deleteLater() + except: + workfile_dialog = WorkfileLockDialog(filepath) + if not workfile_dialog.exec_(): + cmds.file(new=True) + return create_workfile_lock(filepath) @@ -514,7 +531,6 @@ def on_before_close(): def before_file_open(): """check lock file when the file changed""" - log.info("Removing lock on current file before scene open...") # delete the lock file _remove_workfile_lock() @@ -654,6 +670,13 @@ def before_workfile_save(event): create_workspace_mel(workdir_path, project_name) +def after_workfile_save(event): + workfile_name = event["filename"] + if workfile_name: + if not is_workfile_locked(workfile_name): + create_workfile_lock(workfile_name) + + class MayaDirmap(HostDirmap): def on_enable_dirmap(self): cmds.dirmap(en=True) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 93cc0b153b..7377d10171 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -469,9 +469,7 @@ class FilesWidget(QtWidgets.QWidget): host = self.host if self._is_workfile_locked(filepath): # add lockfile dialog - dialog = WorkfileLockDialog(filepath, parent=self) - if not dialog.exec_(): - return + WorkfileLockDialog(filepath) if isinstance(host, IWorkfileHost): has_unsaved_changes = host.workfile_has_unsaved_changes() From 775b34df06b6af95e87d031576a7bea7a4bd7ef5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 14 Sep 2022 20:53:22 +0800 Subject: [PATCH 362/513] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index eb22eeeb3b..969680bdf5 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -155,8 +155,11 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): OpenMaya.MSceneMessage.kBeforeSave, _on_scene_save ) - self._op_events[_after_scene_save] = OpenMaya.MSceneMessage.addCallback( - OpenMaya.MSceneMessage.kAfterSave, _after_scene_save + self._op_events[_after_scene_save] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterSave, + _after_scene_save + ) ) self._op_events[_before_scene_save] = ( @@ -243,9 +246,11 @@ def _on_maya_initialized(*args): def _on_scene_new(*args): emit_event("new") + def _after_scene_save(*arg): emit_event("after.save") + def _on_scene_save(*args): emit_event("save") @@ -508,14 +513,10 @@ def check_lock_on_current_file(): if is_workfile_locked(filepath): # add lockfile dialog - try: - workfile_dialog.close() - workfile_dialog.deleteLater() - except: - workfile_dialog = WorkfileLockDialog(filepath) - if not workfile_dialog.exec_(): - cmds.file(new=True) - return + workfile_dialog = WorkfileLockDialog(filepath) + if not workfile_dialog.exec_(): + cmds.file(new=True) + return create_workfile_lock(filepath) From 8e068308c6369f50d220a58a81288f1f57337365 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 15:30:46 +0200 Subject: [PATCH 363/513] Add Display Textures settings correctly, labelize the Show settings to clarify what they are --- .../schemas/schema_maya_capture.json | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 7a40f349cc..ae6c428faf 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -195,6 +195,11 @@ { "nolights": "No Lights"} ] }, + { + "type": "boolean", + "key": "displayTextures", + "label": "Display Textures" + }, { "type": "number", "key": "textureMaxResolution", @@ -217,11 +222,6 @@ "key": "shadows", "label": "Display Shadows" }, - { - "type": "boolean", - "key": "textures", - "label": "Display Textures" - }, { "type": "boolean", "key": "twoSidedLighting", @@ -372,67 +372,67 @@ { "type": "boolean", "key": "cameras", - "label": "cameras" + "label": "Cameras" }, { "type": "boolean", "key": "clipGhosts", - "label": "clipGhosts" + "label": "Clip Ghosts" }, { "type": "boolean", "key": "controlVertices", - "label": "controlVertices" + "label": "NURBS CVs" }, { "type": "boolean", "key": "deformers", - "label": "deformers" + "label": "Deformers" }, { "type": "boolean", "key": "dimensions", - "label": "dimensions" + "label": "Dimensions" }, { "type": "boolean", "key": "dynamicConstraints", - "label": "dynamicConstraints" + "label": "Dynamic Constraints" }, { "type": "boolean", "key": "dynamics", - "label": "dynamics" + "label": "Dynamics" }, { "type": "boolean", "key": "fluids", - "label": "fluids" + "label": "Fluids" }, { "type": "boolean", "key": "follicles", - "label": "follicles" + "label": "Follicles" }, { "type": "boolean", "key": "gpuCacheDisplayFilter", - "label": "gpuCacheDisplayFilter" + "label": "GPU Cache" }, { "type": "boolean", "key": "greasePencils", - "label": "greasePencils" + "label": "Grease Pencil" }, { "type": "boolean", "key": "grid", - "label": "grid" + "label": "Grid" }, { "type": "boolean", "key": "hairSystems", - "label": "hairSystems" + "label": "Hair Systems" }, { "type": "boolean", @@ -442,47 +442,47 @@ { "type": "boolean", "key": "hud", - "label": "hud" + "label": "HUD" }, { "type": "boolean", "key": "hulls", - "label": "hulls" + "label": "NURBS Hulls" }, { "type": "boolean", "key": "ikHandles", - "label": "ikHandles" + "label": "IK Handles" }, { "type": "boolean", "key": "imagePlane", - "label": "imagePlane" + "label": "Image Planes" }, { "type": "boolean", "key": "joints", - "label": "joints" + "label": "Joints" }, { "type": "boolean", "key": "lights", - "label": "lights" + "label": "Lights" }, { "type": "boolean", "key": "locators", - "label": "locators" + "label": "Locators" }, { "type": "boolean", "key": "manipulators", - "label": "manipulators" + "label": "Manipulators" }, { "type": "boolean", "key": "motionTrails", - "label": "motionTrails" + "label": "Motion Trails" }, { "type": "boolean", @@ -502,47 +502,52 @@ { "type": "boolean", "key": "nurbsCurves", - "label": "nurbsCurves" + "label": "NURBS Curves" }, { "type": "boolean", "key": "nurbsSurfaces", - "label": "nurbsSurfaces" + "label": "NURBS Surfaces" }, { "type": "boolean", "key": "particleInstancers", - "label": "particleInstancers" + "label": "Particle Instancers" }, { "type": "boolean", "key": "pivots", - "label": "pivots" + "label": "Pivots" }, { "type": "boolean", "key": "planes", - "label": "planes" + "label": "Planes" }, { "type": "boolean", "key": "pluginShapes", - "label": "pluginShapes" + "label": "Plugin Shapes" }, { "type": "boolean", "key": "polymeshes", - "label": "polymeshes" + "label": "Polygons" }, { "type": "boolean", "key": "strokes", - "label": "strokes" + "label": "Strokes" }, { "type": "boolean", "key": "subdivSurfaces", - "label": "subdivSurfaces" + "label": "Subdiv Surfaces" + }, + { + "type": "boolean", + "key": "textures", + "label": "Texture Placements" } ] }, From c6bd26485d191406e96288c7a4ea7e99f2364494 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 15:35:12 +0200 Subject: [PATCH 364/513] Sort a bit more by Label again so that NURBS options are together + fix label for handles --- .../schemas/schema_maya_capture.json | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index ae6c428faf..d2627c1e2a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -379,11 +379,6 @@ "key": "clipGhosts", "label": "Clip Ghosts" }, - { - "type": "boolean", - "key": "controlVertices", - "label": "NURBS CVs" - }, { "type": "boolean", "key": "deformers", @@ -437,18 +432,13 @@ { "type": "boolean", "key": "handles", - "label": "handles" + "label": "Handles" }, { "type": "boolean", "key": "hud", "label": "HUD" }, - { - "type": "boolean", - "key": "hulls", - "label": "NURBS Hulls" - }, { "type": "boolean", "key": "ikHandles", @@ -499,11 +489,21 @@ "key": "nRigids", "label": "nRigids" }, + { + "type": "boolean", + "key": "controlVertices", + "label": "NURBS CVs" + }, { "type": "boolean", "key": "nurbsCurves", "label": "NURBS Curves" }, + { + "type": "boolean", + "key": "hulls", + "label": "NURBS Hulls" + }, { "type": "boolean", "key": "nurbsSurfaces", From fd4648c9bd48bda91e1c259a5903f4f263668b78 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 15:47:43 +0200 Subject: [PATCH 365/513] Add label --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index d2627c1e2a..18e69e92c3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -369,6 +369,10 @@ { "type": "splitter" }, + { + "type": "label", + "label": "Show" + }, { "type": "boolean", "key": "cameras", From 522d1e2df837bda7611b9667c1afd127337d945f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:04:09 +0200 Subject: [PATCH 366/513] Labelize Camera options to match with Camera attributes in Attribute Editor --- .../schemas/schema_maya_capture.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 18e69e92c3..8c2a460871 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -564,47 +564,47 @@ { "type": "boolean", "key": "displayGateMask", - "label": "displayGateMask" + "label": "Display Gate Mask" }, { "type": "boolean", "key": "displayResolution", - "label": "displayResolution" + "label": "Display Resolution" }, { "type": "boolean", "key": "displayFilmGate", - "label": "displayFilmGate" + "label": "Display Film Gate" }, { "type": "boolean", "key": "displayFieldChart", - "label": "displayFieldChart" + "label": "Display Field Chart" }, { "type": "boolean", "key": "displaySafeAction", - "label": "displaySafeAction" + "label": "Display Safe Action" }, { "type": "boolean", "key": "displaySafeTitle", - "label": "displaySafeTitle" + "label": "Display Safe Title" }, { "type": "boolean", "key": "displayFilmPivot", - "label": "displayFilmPivot" + "label": "Display Film Pivot" }, { "type": "boolean", "key": "displayFilmOrigin", - "label": "displayFilmOrigin" + "label": "Display Film Origin" }, { "type": "number", "key": "overscan", - "label": "overscan", + "label": "Overscan", "decimal": 1, "minimum": 0, "maximum": 10 From 9b9bfdadb993c43c2f6d1232ef522b5326df2cce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:05:10 +0200 Subject: [PATCH 367/513] Uppercase `percent` label like the surrounding labels --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 8c2a460871..32987e7423 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -157,7 +157,7 @@ { "type": "number", "key": "percent", - "label": "percent", + "label": "Percent", "decimal": 1, "minimum": 0, "maximum": 200 From f501dac15ff4d3f0e30b4db4af5caf3485a90c7a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:14:18 +0200 Subject: [PATCH 368/513] Fix default settings for new viewport options settings --- openpype/settings/defaults/project_settings/maya.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 99ba4cdd5c..8706ea995f 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -693,10 +693,10 @@ "Viewport Options": { "override_viewport_options": true, "displayLights": "default", + "displayTextures": true, "textureMaxResolution": 1024, "renderDepthOfField": true, "shadows": true, - "textures": true, "twoSidedLighting": true, "lineAAEnable": true, "multiSample": 8, @@ -719,7 +719,6 @@ "motionBlurShutterOpenFraction": 0.2, "cameras": false, "clipGhosts": false, - "controlVertices": false, "deformers": false, "dimensions": false, "dynamicConstraints": false, @@ -732,7 +731,6 @@ "hairSystems": true, "handles": false, "hud": false, - "hulls": false, "ikHandles": false, "imagePlane": true, "joints": false, @@ -743,7 +741,9 @@ "nCloths": false, "nParticles": false, "nRigids": false, + "controlVertices": false, "nurbsCurves": false, + "hulls": false, "nurbsSurfaces": false, "particleInstancers": false, "pivots": false, @@ -751,7 +751,8 @@ "pluginShapes": false, "polymeshes": true, "strokes": false, - "subdivSurfaces": false + "subdivSurfaces": false, + "textures": false }, "Camera Options": { "displayGateMask": false, From b9c3c95c2642b19305a57064be74fa0cd6ef12ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:16:03 +0200 Subject: [PATCH 369/513] Use `id` variable (cosmetics because it results in same key) --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 58e160cb2f..6a8447d6ad 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2483,7 +2483,7 @@ def load_capture_preset(data=None): # DISPLAY OPTIONS id = 'Display Options' disp_options = {} - for key in preset['Display Options']: + for key in preset[id]: if key.startswith('background'): disp_options[key] = preset['Display Options'][key] if len(disp_options[key]) == 4: From 95fef2c4b11e055be8d55bd464e075eb2f1d7415 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:50:32 +0200 Subject: [PATCH 370/513] Fix Width label --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 570e22aa60..ffa1e61e68 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -141,7 +141,7 @@ { "type": "number", "key": "width", - "label": " Width", + "label": "Width", "decimal": 0, "minimum": 0, "maximum": 99999 From 1e27e9b71ff18af0aa7d957be6db344f7030a2aa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:50:50 +0200 Subject: [PATCH 371/513] Remove unused settings --- .../schemas/schema_maya_capture.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index ffa1e61e68..2e4d4d67ab 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -153,19 +153,6 @@ "decimal": 0, "minimum": 0, "maximum": 99999 - }, - { - "type": "number", - "key": "percent", - "label": "Percent", - "decimal": 1, - "minimum": 0, - "maximum": 200 - }, - { - "type": "text", - "key": "mode", - "label": "Mode" } ] }, From ee4b9056feae4f942624d6e9bf37bb64a875bfba Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:51:59 +0200 Subject: [PATCH 372/513] Fix incorrectly resolved merge conflict --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 2e4d4d67ab..e23dbbbc1d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -430,11 +430,6 @@ "key": "headsUpDisplay", "label": "HUD" }, - { - "type": "boolean", - "key": "hulls", - "label": "hulls" - }, { "type": "boolean", "key": "ikHandles", From e16f5df4d7a346ee5b1f7b3c79b146fe9ba3e958 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:53:04 +0200 Subject: [PATCH 373/513] Update defaults for the removed settings --- openpype/settings/defaults/project_settings/maya.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 8b0418f5c6..79e80aec2e 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -686,9 +686,7 @@ }, "Resolution": { "width": 1920, - "height": 1080, - "percent": 1.0, - "mode": "Custom" + "height": 1080 }, "Viewport Options": { "override_viewport_options": true, From 730f451020cb438b6a57756e5713212ae6e2261f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:57:11 +0200 Subject: [PATCH 374/513] Revert "Fix Width label" This reverts commit 95fef2c4b11e055be8d55bd464e075eb2f1d7415. --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index e23dbbbc1d..c9904150fd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -141,7 +141,7 @@ { "type": "number", "key": "width", - "label": "Width", + "label": " Width", "decimal": 0, "minimum": 0, "maximum": 99999 From a0333c88aed89707eba5cbea154f4449639dac44 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 17:23:59 +0200 Subject: [PATCH 375/513] Remove unused PanZoom / pan_zoom settings --- .../settings/defaults/project_settings/maya.json | 3 --- .../projects_schema/schemas/schema_maya_capture.json | 12 ------------ 2 files changed, 15 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 79e80aec2e..8643297f02 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -678,9 +678,6 @@ "isolate_view": true, "off_screen": true }, - "PanZoom": { - "pan_zoom": true - }, "Renderer": { "rendererName": "vp2Renderer" }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index c9904150fd..62c33f55fc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -94,18 +94,6 @@ } ] }, - - { - "type": "dict", - "key": "PanZoom", - "children": [ - { - "type": "boolean", - "key": "pan_zoom", - "label": " Pan Zoom" - } - ] - }, { "type": "splitter" }, From 3703ec07bcc709fb69cc57b19a66dc449c88f425 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Sep 2022 18:23:17 +0200 Subject: [PATCH 376/513] OP-3940 - introduced new Settings for CollectVersion for Photoshop --- .../defaults/project_settings/photoshop.json | 3 +++ .../schema_project_photoshop.json | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 552c2c9cad..8ea36a3000 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -15,6 +15,9 @@ "CollectInstances": { "flatten_subset_template": "" }, + "CollectVersion": { + "sync_workfile_version": true + }, "ValidateContainers": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 7aa49c99a4..500c5d027b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -131,6 +131,23 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CollectVersion", + "label": "Collect Version", + "children": [ + { + "type": "label", + "label": "Synchronize version for image and review instances by workfile version." + }, + { + "type": "boolean", + "key": "sync_workfile_version", + "label": "Synchronize version with workfile" + } + ] + }, { "type": "schema_template", "name": "template_publish_plugin", From d93a18fd89bf749de62413b9280a21dde871c319 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Sep 2022 18:25:11 +0200 Subject: [PATCH 377/513] OP-3940 - added new collector for Photoshop Single point of control if image and review instances should have their version synchronized according to workfile version. --- .../hosts/photoshop/plugins/CollectVersion.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 openpype/hosts/photoshop/plugins/CollectVersion.py diff --git a/openpype/hosts/photoshop/plugins/CollectVersion.py b/openpype/hosts/photoshop/plugins/CollectVersion.py new file mode 100644 index 0000000000..46f48b20fb --- /dev/null +++ b/openpype/hosts/photoshop/plugins/CollectVersion.py @@ -0,0 +1,28 @@ +import pyblish.api + + +class CollectVersion(pyblish.api.InstancePlugin): + """Collect version for publishable instances. + + Used to synchronize version from workfile to all publishable instances: + - image (manually created or color coded) + - review + + Dev comment: + Explicit collector created to control this from single place and not from + 3 different. + """ + order = pyblish.api.CollectorOrder + 0.200 + label = 'Collect Version' + + hosts = ["photoshop"] + families = ["image", "review"] + + # controlled by Settings + sync_workfile_version = False + + def process(self, instance): + if self.sync_workfile_version: + workfile_version = instance.context.data["version"] + self.log.debug(f"Applying version {workfile_version}") + instance.data["version"] = workfile_version From d0b8437cfd86113c6ffa3a04a0959bc80f3ddfcc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Sep 2022 18:32:08 +0200 Subject: [PATCH 378/513] OP-3940 - added optionality to collector There might be a reason when artist would like to skip this synchronization for specific workfile. --- openpype/hosts/photoshop/plugins/CollectVersion.py | 1 + openpype/settings/defaults/project_settings/photoshop.json | 1 + .../schemas/projects_schema/schema_project_photoshop.json | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/openpype/hosts/photoshop/plugins/CollectVersion.py b/openpype/hosts/photoshop/plugins/CollectVersion.py index 46f48b20fb..bc7af580d7 100644 --- a/openpype/hosts/photoshop/plugins/CollectVersion.py +++ b/openpype/hosts/photoshop/plugins/CollectVersion.py @@ -19,6 +19,7 @@ class CollectVersion(pyblish.api.InstancePlugin): families = ["image", "review"] # controlled by Settings + optional = True sync_workfile_version = False def process(self, instance): diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 8ea36a3000..43a460052a 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -16,6 +16,7 @@ "flatten_subset_template": "" }, "CollectVersion": { + "optional": true, "sync_workfile_version": true }, "ValidateContainers": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 500c5d027b..e8dad84859 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -137,6 +137,11 @@ "key": "CollectVersion", "label": "Collect Version", "children": [ + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, { "type": "label", "label": "Synchronize version for image and review instances by workfile version." From 790d350d7f0c9a90fae28a02e0bfea57d1746e4a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 14 Sep 2022 22:17:50 +0200 Subject: [PATCH 379/513] fix: retimed attributes integration --- .../publish/extract_subset_resources.py | 27 +++++++++++-------- .../publish/collect_otio_frame_ranges.py | 12 +++++++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 1d42330e23..0774c401c0 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -90,7 +90,7 @@ class ExtractSubsetResources(openpype.api.Extractor): handle_end = instance.data["handleEnd"] handles = max(handle_start, handle_end) include_handles = instance.data.get("includeHandles") - retimed_handles = instance.data.get("retimedHandles") + not_retimed_handles = instance.data.get("notRetimedHandles") # get media source range with handles source_start_handles = instance.data["sourceStartH"] @@ -98,7 +98,15 @@ class ExtractSubsetResources(openpype.api.Extractor): # retime if needed if r_speed != 1.0: - if retimed_handles: + if not_retimed_handles: + # handles are not retimed + source_end_handles = ( + source_start_handles + + (r_source_dur - 1) + + handle_start + + handle_end + ) + else: # handles are retimed source_start_handles = ( instance.data["sourceStart"] - r_handle_start) @@ -108,20 +116,12 @@ class ExtractSubsetResources(openpype.api.Extractor): + r_handle_start + r_handle_end ) - else: - # handles are not retimed - source_end_handles = ( - source_start_handles - + (r_source_dur - 1) - + handle_start - + handle_end - ) # get frame range with handles for representation range frame_start_handle = frame_start - handle_start repre_frame_start = frame_start_handle if include_handles: - if r_speed == 1.0 or not retimed_handles: + if r_speed == 1.0 or not_retimed_handles: frame_start_handle = frame_start else: frame_start_handle = ( @@ -167,6 +167,11 @@ class ExtractSubsetResources(openpype.api.Extractor): - (r_handle_start + r_handle_end) ) }) + if not_retimed_handles: + instance.data["versionData"].update({ + "handleStart": handle_start, + "handleEnd": handle_end + }) self.log.debug("_ i_version_data: {}".format( instance.data["versionData"] )) diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index cfb0318950..bfd5320c25 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -10,6 +10,7 @@ import opentimelineio as otio import pyblish.api from pprint import pformat from openpype.pipeline.editorial import ( + get_media_range_with_retimes, otio_range_to_frame_range, otio_range_with_handles ) @@ -57,8 +58,15 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): # in case of retimed clip and frame range should not be retimed if workfile_source_duration: - frame_end = frame_start + otio.opentime.to_frames( - otio_src_range.duration, otio_src_range.duration.rate) - 1 + # get available range trimmed with processed retimes + retimed_attributes = get_media_range_with_retimes( + otio_clip, 0, 0) + self.log.debug( + ">> retimed_attributes: {}".format(retimed_attributes)) + media_in = int(retimed_attributes["mediaIn"]) + media_out = int(retimed_attributes["mediaOut"]) + frame_end = frame_start + (media_out - media_in) + 1 + self.log.debug(frame_end) data = { "frameStart": frame_start, From be0734684918130469a775fc67d21c8684620f5b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 14 Sep 2022 22:18:07 +0200 Subject: [PATCH 380/513] flame: settings for retimed attributes --- .../hosts/flame/plugins/create/create_shot_clip.py | 4 ++-- openpype/settings/defaults/project_settings/flame.json | 4 +++- .../schemas/projects_schema/schema_project_flame.json | 10 ++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index b03a39a7ca..7622ff217c 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -23,10 +23,10 @@ class CreateShotClip(opfapi.Creator): # nested dictionary (only one level allowed # for sections and dict) for _k, _v in v["value"].items(): - if presets.get(_k): + if presets.get(_k, None) is not None: gui_inputs[k][ "value"][_k]["value"] = presets[_k] - if presets.get(k): + if presets.get(_k, None) is not None: gui_inputs[k]["value"] = presets[k] # open widget for plugins inputs diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index bfdc58d9ee..c90193fe13 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -17,7 +17,9 @@ "workfileFrameStart": 1001, "handleStart": 5, "handleEnd": 5, - "includeHandles": false + "includeHandles": false, + "retimedHandles": true, + "retimedFramerange": true } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index ca62679b3d..5f05bef0e1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -128,6 +128,16 @@ "type": "boolean", "key": "includeHandles", "label": "Enable handles including" + }, + { + "type": "boolean", + "key": "retimedHandles", + "label": "Enable retimed handles" + }, + { + "type": "boolean", + "key": "retimedFramerange", + "label": "Enable retimed shot frameranges" } ] } From 9ab7647d55b89919043efee3e36852607a1348b9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 10:57:19 +0200 Subject: [PATCH 381/513] OP-3940 - fixed location and name --- .../plugins/{CollectVersion.py => publish/collect_version.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/photoshop/plugins/{CollectVersion.py => publish/collect_version.py} (100%) diff --git a/openpype/hosts/photoshop/plugins/CollectVersion.py b/openpype/hosts/photoshop/plugins/publish/collect_version.py similarity index 100% rename from openpype/hosts/photoshop/plugins/CollectVersion.py rename to openpype/hosts/photoshop/plugins/publish/collect_version.py From 7a5d20ffdb05347736475c86b87e6a782fb5d80f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 15 Sep 2022 11:00:20 +0200 Subject: [PATCH 382/513] :bug: skip plugin if otioTimeline is missing --- openpype/plugins/publish/extract_otio_file.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_otio_file.py b/openpype/plugins/publish/extract_otio_file.py index c692205d81..1a6a82117d 100644 --- a/openpype/plugins/publish/extract_otio_file.py +++ b/openpype/plugins/publish/extract_otio_file.py @@ -16,6 +16,8 @@ class ExtractOTIOFile(publish.Extractor): hosts = ["resolve", "hiero", "traypublisher"] def process(self, instance): + if not instance.context.data.get("otioTimeline"): + return # create representation data if "representations" not in instance.data: instance.data["representations"] = [] From c97602341fbeb6ef40c46143619f56f693c36588 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 11:53:49 +0200 Subject: [PATCH 383/513] flame: fix creator preset detection --- openpype/hosts/flame/plugins/create/create_shot_clip.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 7622ff217c..835201cd3b 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -26,7 +26,8 @@ class CreateShotClip(opfapi.Creator): if presets.get(_k, None) is not None: gui_inputs[k][ "value"][_k]["value"] = presets[_k] - if presets.get(_k, None) is not None: + + if presets.get(k, None) is not None: gui_inputs[k]["value"] = presets[k] # open widget for plugins inputs From fa65e20ff7f4ff843ca54ac97897f33567c89eee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 12:09:59 +0200 Subject: [PATCH 384/513] Flame: open folder and files after project is created --- openpype/hosts/flame/hooks/pre_flame_setup.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index ad2b0dc897..9c2ad709c7 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -22,6 +22,7 @@ class FlamePrelaunch(PreLaunchHook): in environment var FLAME_SCRIPT_DIR. """ app_groups = ["flame"] + permisisons = 0o777 wtc_script_path = os.path.join( opflame.HOST_DIR, "api", "scripts", "wiretap_com.py") @@ -38,6 +39,7 @@ class FlamePrelaunch(PreLaunchHook): """Hook entry method.""" project_doc = self.data["project_doc"] project_name = project_doc["name"] + volume_name = _env.get("FLAME_WIRETAP_VOLUME") # get image io project_anatomy = self.data["anatomy"] @@ -81,7 +83,7 @@ class FlamePrelaunch(PreLaunchHook): data_to_script = { # from settings "host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname, - "volume_name": _env.get("FLAME_WIRETAP_VOLUME"), + "volume_name": volume_name, "group_name": _env.get("FLAME_WIRETAP_GROUP"), "color_policy": str(imageio_flame["project"]["colourPolicy"]), @@ -99,8 +101,39 @@ class FlamePrelaunch(PreLaunchHook): app_arguments = self._get_launch_arguments(data_to_script) + # fix project data permission issue + self._fix_permissions(project_name, volume_name) + self.launch_context.launch_args.extend(app_arguments) + def _fix_permissions(self, project_name, volume_name): + """Work around for project data permissions + + Reported issue: when project is created locally on one machine, + it is impossible to migrate it to other machine. Autodesk Flame + is crating some unmanagable files which needs to be opened to 0o777. + + Args: + project_name (str): project name + volume_name (str): studio volume + """ + dirs_to_modify = [ + "/usr/discreet/project/{}".format(project_name), + "/opt/Autodesk/clip/{}/{}.prj".format(volume_name, project_name), + "/usr/discreet/clip/{}/{}.prj".format(volume_name, project_name) + ] + + for dirtm in dirs_to_modify: + for root, dirs, files in os.walk(dirtm): + try: + for d in dirs: + os.chmod(os.path.join(root, d), self.permisisons) + for f in files: + os.chmod(os.path.join(root, f), self.permisisons) + except OSError as _E: + self.log.warning("Not able to open files: {}".format(_E)) + + def _get_flame_fps(self, fps_num): fps_table = { float(23.976): "23.976 fps", From d905740208168114b12ec1a4873601d61c9b1798 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 12:18:07 +0200 Subject: [PATCH 385/513] OP-3940 - collector cannot be optional --- openpype/settings/defaults/project_settings/photoshop.json | 1 - .../schemas/projects_schema/schema_project_photoshop.json | 5 ----- 2 files changed, 6 deletions(-) diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 43a460052a..8ea36a3000 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -16,7 +16,6 @@ "flatten_subset_template": "" }, "CollectVersion": { - "optional": true, "sync_workfile_version": true }, "ValidateContainers": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index e8dad84859..500c5d027b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -137,11 +137,6 @@ "key": "CollectVersion", "label": "Collect Version", "children": [ - { - "type": "boolean", - "key": "optional", - "label": "Optional" - }, { "type": "label", "label": "Synchronize version for image and review instances by workfile version." From 18e03a8d28bc9e9fb0404c5f92475861e90f4f17 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 12:46:31 +0200 Subject: [PATCH 386/513] OP-3940 - collector cannot be optional --- openpype/hosts/photoshop/plugins/publish/collect_version.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_version.py b/openpype/hosts/photoshop/plugins/publish/collect_version.py index bc7af580d7..46f48b20fb 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_version.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_version.py @@ -19,7 +19,6 @@ class CollectVersion(pyblish.api.InstancePlugin): families = ["image", "review"] # controlled by Settings - optional = True sync_workfile_version = False def process(self, instance): From 8a21fdfcf25a3294b53e742dff84eb82950768e7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 12:48:35 +0200 Subject: [PATCH 387/513] OP-3682 - Hound --- common/openpype_common/distribution/addon_distribution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/openpype_common/distribution/addon_distribution.py b/common/openpype_common/distribution/addon_distribution.py index e39ce66a0a..ac9c69deca 100644 --- a/common/openpype_common/distribution/addon_distribution.py +++ b/common/openpype_common/distribution/addon_distribution.py @@ -71,7 +71,7 @@ class AddonDownloader: Args: source (dict): {type:"http", "url":"https://} ...} destination (str): local folder to unzip - Retursn: + Returns: (str) local path to addon zip file """ pass @@ -235,4 +235,4 @@ def check_addons(server_endpoint, addon_folder, downloaders): def cli(*args): - raise NotImplemented + raise NotImplementedError From ed29c38cdcaa446bdfa0d0f8300e987d253e09f1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 13:11:42 +0200 Subject: [PATCH 388/513] flame: turn double negative logic to readable code --- .../publish/extract_subset_resources.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 0774c401c0..a8d3201896 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -1,7 +1,6 @@ import os import re import tempfile -from pprint import pformat from copy import deepcopy import pyblish.api @@ -90,7 +89,7 @@ class ExtractSubsetResources(openpype.api.Extractor): handle_end = instance.data["handleEnd"] handles = max(handle_start, handle_end) include_handles = instance.data.get("includeHandles") - not_retimed_handles = instance.data.get("notRetimedHandles") + retimed_handles = instance.data.get("retimedHandles") # get media source range with handles source_start_handles = instance.data["sourceStartH"] @@ -98,15 +97,7 @@ class ExtractSubsetResources(openpype.api.Extractor): # retime if needed if r_speed != 1.0: - if not_retimed_handles: - # handles are not retimed - source_end_handles = ( - source_start_handles - + (r_source_dur - 1) - + handle_start - + handle_end - ) - else: + if retimed_handles: # handles are retimed source_start_handles = ( instance.data["sourceStart"] - r_handle_start) @@ -117,11 +108,20 @@ class ExtractSubsetResources(openpype.api.Extractor): + r_handle_end ) + else: + # handles are not retimed + source_end_handles = ( + source_start_handles + + (r_source_dur - 1) + + handle_start + + handle_end + ) + # get frame range with handles for representation range frame_start_handle = frame_start - handle_start repre_frame_start = frame_start_handle if include_handles: - if r_speed == 1.0 or not_retimed_handles: + if r_speed == 1.0 or not retimed_handles: frame_start_handle = frame_start else: frame_start_handle = ( From ecc6f3ae0e2e4a24cafde6cd13f4959403bc9ada Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 13:12:04 +0200 Subject: [PATCH 389/513] flame: fixing logic --- .../flame/plugins/publish/collect_timeline_instances.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index d6ff13b059..76d48dded2 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -131,9 +131,8 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): "fps": self.fps, "workfileFrameStart": workfile_start, "sourceFirstFrame": int(first_frame), - "notRetimedHandles": ( - not marker_data.get("retimedHandles")), - "notRetimedFramerange": ( + "retimedHandles": marker_data.get("retimedHandles"), + "shotDurationFromSource": ( not marker_data.get("retimedFramerange")), "path": file_path, "flameAddTasks": self.add_tasks, From b635749a7748880df74ea65927148e833c7d7e98 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 13:12:23 +0200 Subject: [PATCH 390/513] global: improving code readibility --- openpype/plugins/publish/collect_otio_frame_ranges.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index bfd5320c25..9a68b6e43d 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -30,7 +30,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): # get basic variables otio_clip = instance.data["otioClip"] workfile_start = instance.data["workfileFrameStart"] - workfile_source_duration = instance.data.get("notRetimedFramerange") + workfile_source_duration = instance.data.get("shotDurationFromSource") # get ranges otio_tl_range = otio_clip.range_in_parent() From 4c0f629e386794b26c8b48fbe30ea0b6599f752d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 13:13:54 +0200 Subject: [PATCH 391/513] flame: missing variable fix --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index a8d3201896..7adcd1453e 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -167,7 +167,7 @@ class ExtractSubsetResources(openpype.api.Extractor): - (r_handle_start + r_handle_end) ) }) - if not_retimed_handles: + if not retimed_handles: instance.data["versionData"].update({ "handleStart": handle_start, "handleEnd": handle_end From 9fdb71b814011dc9f94fa9243711660d3854bbfa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 15 Sep 2022 13:52:29 +0200 Subject: [PATCH 392/513] increasi size of publisher's main window --- openpype/tools/publisher/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 90a36b4f01..2a0e6e940a 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -30,8 +30,8 @@ from .widgets import ( class PublisherWindow(QtWidgets.QDialog): """Main window of publisher.""" - default_width = 1000 - default_height = 600 + default_width = 1200 + default_height = 700 def __init__(self, parent=None, reset_on_show=None): super(PublisherWindow, self).__init__(parent) From cfe997dac7a519500ec19834ead6963d7e01ebc8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 14:16:40 +0200 Subject: [PATCH 393/513] flame: fixing ls() --- openpype/hosts/flame/api/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index da44be1b15..324d13bc3f 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -90,8 +90,7 @@ def containerise(flame_clip_segment, def ls(): """List available containers. """ - # TODO: ls - pass + return [] def parse_container(tl_segment, validate=True): @@ -107,6 +106,7 @@ def update_container(tl_segment, data=None): # TODO: update_container pass + def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle node passthrough states on instance toggles.""" From dd0bbf2bb515441e730c7fea6e3f762ae1f186b2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 15:04:55 +0200 Subject: [PATCH 394/513] adding running version into issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6ed6ae428c..d1e98409c5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,6 +6,8 @@ labels: bug assignees: '' --- +**Running version** +openpype-v3.14.1-nightly.2 **Describe the bug** A clear and concise description of what the bug is. From ee154aca7f73df163a2e3fe361d87f2f7264ca6d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 15:06:27 +0200 Subject: [PATCH 395/513] issue: improving example string --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d1e98409c5..96e768e420 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,7 +7,7 @@ assignees: '' --- **Running version** -openpype-v3.14.1-nightly.2 +[ex. 3.14.1-nightly.2] **Describe the bug** A clear and concise description of what the bug is. From b405299e92fe718993591892c71d59f76a604b5d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 15:33:57 +0200 Subject: [PATCH 396/513] OP-3940 - added collector for remote publishes In Webpublisher uploaded workfile name is not relieable, use last published + 1 instead. --- .../publish/collect_published_version.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 openpype/hosts/photoshop/plugins/publish/collect_published_version.py diff --git a/openpype/hosts/photoshop/plugins/publish/collect_published_version.py b/openpype/hosts/photoshop/plugins/publish/collect_published_version.py new file mode 100644 index 0000000000..2502689e4b --- /dev/null +++ b/openpype/hosts/photoshop/plugins/publish/collect_published_version.py @@ -0,0 +1,55 @@ +"""Collects published version of workfile and increments it. + +For synchronization of published image and workfile version it is required +to store workfile version from workfile file name in context.data["version"]. +In remote publishing this name is unreliable (artist might not follow naming +convention etc.), last published workfile version for particular workfile +subset is used instead. + +This plugin runs only in remote publishing (eg. Webpublisher). + +Requires: + context.data["assetEntity"] + +Provides: + context["version"] - incremented latest published workfile version +""" + +import pyblish.api + +from openpype.client import get_last_version_by_subset_name + + +class CollectPublishedVersion(pyblish.api.ContextPlugin): + """Collects published version of workfile and increments it.""" + + order = pyblish.api.CollectorOrder + 0.190 + label = "Collect published version" + hosts = ["photoshop"] + targets = ["remotepublish"] + + def process(self, context): + workfile_subset_name = None + for instance in context: + if instance.data["family"] == "workfile": + workfile_subset_name = instance.data["subset"] + break + + if not workfile_subset_name: + self.log.warning("No workfile instance found, " + "synchronization of version will not work.") + return + + project_name = context.data["projectName"] + asset_doc = context.data["assetEntity"] + asset_id = asset_doc["_id"] + + version_doc = get_last_version_by_subset_name(project_name, + workfile_subset_name, + asset_id) + version_int = 1 + if version_doc: + version_int += int(version_doc["name"]) + + self.log.debug(f"Setting {version_int} to context.") + context.data["version"] = version_int From abee82f45e48205c2e995dda9f53d8e92370cfed Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 15:35:09 +0200 Subject: [PATCH 397/513] OP-3940 - fix wrong import for pype_commands --- openpype/pype_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 85561495fd..f65d969c53 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -187,7 +187,7 @@ class PypeCommands: (to choose validator for example) """ - from openpype.hosts.webpublisher.cli_functions import ( + from openpype.hosts.webpublisher.publish_functions import ( cli_publish_from_app ) From d1d2d05ec6b8e62e6765b9e1b17ef6f7d9ba950d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 16:15:14 +0200 Subject: [PATCH 398/513] flame: version frame start was wrong if handles included was off --- .../publish/extract_subset_resources.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 7adcd1453e..1b7e9b88b5 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -136,6 +136,9 @@ class ExtractSubsetResources(openpype.api.Extractor): source_duration_handles = ( source_end_handles - source_start_handles) + 1 + self.log.debug("_ source_duration_handles: {}".format( + source_duration_handles)) + # create staging dir path staging_dir = self.staging_dir(instance) @@ -159,18 +162,28 @@ class ExtractSubsetResources(openpype.api.Extractor): if version_data: instance.data["versionData"].update(version_data) + # version data start frame + vd_frame_start = frame_start + if include_handles: + vd_frame_start = frame_start_handle + if r_speed != 1.0: instance.data["versionData"].update({ - "frameStart": frame_start_handle, + "frameStart": vd_frame_start, "frameEnd": ( - (frame_start_handle + source_duration_handles - 1) + (vd_frame_start + source_duration_handles - 1) - (r_handle_start + r_handle_end) ) }) if not retimed_handles: instance.data["versionData"].update({ "handleStart": handle_start, - "handleEnd": handle_end + "handleEnd": handle_end, + "frameStart": vd_frame_start, + "frameEnd": ( + (vd_frame_start + source_duration_handles - 1) + - (handle_start + handle_end) + ) }) self.log.debug("_ i_version_data: {}".format( instance.data["versionData"] From 9403dc743a341e56c44b7afb2f9549d31a3216fa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 15 Sep 2022 23:44:02 +0800 Subject: [PATCH 399/513] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 969680bdf5..c13b47ef4a 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -112,7 +112,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): register_event_callback("taskChanged", on_task_changed) register_event_callback("workfile.open.before", before_workfile_open) register_event_callback("workfile.save.before", before_workfile_save) - register_event_callback("workfile.save.after", after_workfile_save) + register_event_callback("workfile.save.before", after_workfile_save) def open_workfile(self, filepath): return open_file(filepath) @@ -203,7 +203,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): self.log.info("Installed event handler _on_scene_save..") self.log.info("Installed event handler _before_scene_save..") - self.log.info("Insatall event handler _on_after_save..") + self.log.info("Installed event handler _on_after_save..") self.log.info("Installed event handler _on_scene_new..") self.log.info("Installed event handler _on_maya_initialized..") self.log.info("Installed event handler _on_scene_open..") @@ -496,9 +496,7 @@ def on_before_save(): def on_after_save(): """Check if there is a lockfile after save""" - filepath = current_file() - if not is_workfile_locked(filepath): - create_workfile_lock(filepath) + check_lock_on_current_file() def check_lock_on_current_file(): @@ -621,6 +619,7 @@ def on_new(): "from openpype.hosts.maya.api import lib;" "lib.add_render_layer_change_observer()") lib.set_context_settings() + _remove_workfile_lock() def on_task_changed(): @@ -660,12 +659,14 @@ def on_task_changed(): def before_workfile_open(): - _remove_workfile_lock() + if handle_workfile_locks(): + _remove_workfile_lock() def before_workfile_save(event): project_name = legacy_io.active_project() - _remove_workfile_lock() + if handle_workfile_locks(): + _remove_workfile_lock() workdir_path = event["workdir_path"] if workdir_path: create_workspace_mel(workdir_path, project_name) @@ -673,9 +674,10 @@ def before_workfile_save(event): def after_workfile_save(event): workfile_name = event["filename"] - if workfile_name: - if not is_workfile_locked(workfile_name): - create_workfile_lock(workfile_name) + if handle_workfile_locks(): + if workfile_name: + if not is_workfile_locked(workfile_name): + create_workfile_lock(workfile_name) class MayaDirmap(HostDirmap): From 0fb1b9be93de9fe690afc4b1ca6fca2b1f8ce2fd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 17:59:02 +0200 Subject: [PATCH 400/513] OP-3682 - updated AddonSource --- .../distribution/addon_distribution.py | 33 +++++++++++++++++-- .../tests/test_addon_distributtion.py | 8 ++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/common/openpype_common/distribution/addon_distribution.py b/common/openpype_common/distribution/addon_distribution.py index ac9c69deca..be6faab3e6 100644 --- a/common/openpype_common/distribution/addon_distribution.py +++ b/common/openpype_common/distribution/addon_distribution.py @@ -32,21 +32,50 @@ class MultiPlatformPath(object): @attr.s class AddonSource(object): type = attr.ib() - url = attr.ib(default=None) + + +@attr.s +class LocalAddonSource(AddonSource): path = attr.ib(default=attr.Factory(MultiPlatformPath)) +@attr.s +class WebAddonSource(AddonSource): + url = attr.ib(default=None) + + @attr.s class AddonInfo(object): """Object matching json payload from Server""" name = attr.ib() version = attr.ib() - sources = attr.ib(default=attr.Factory(list), type=AddonSource) + sources = attr.ib(default=attr.Factory(list)) hash = attr.ib(default=None) description = attr.ib(default=None) license = attr.ib(default=None) authors = attr.ib(default=None) + @classmethod + def from_dict(cls, data): + sources = [] + for source in data.get("sources", []): + if source.get("type") == UrlType.FILESYSTEM.value: + source_addon = LocalAddonSource(type=source["type"], + path=source["path"]) + if source.get("type") == UrlType.HTTP.value: + source_addon = WebAddonSource(type=source["type"], + url=source["url"]) + + sources.append(source_addon) + + return cls(name=data.get("name"), + version=data.get("version"), + hash=data.get("hash"), + description=data.get("description"), + sources=sources, + license=data.get("license"), + authors=data.get("authors")) + class AddonDownloader: log = logging.getLogger(__name__) diff --git a/common/openpype_common/distribution/tests/test_addon_distributtion.py b/common/openpype_common/distribution/tests/test_addon_distributtion.py index 7dd27fd44f..faf4e01e22 100644 --- a/common/openpype_common/distribution/tests/test_addon_distributtion.py +++ b/common/openpype_common/distribution/tests/test_addon_distributtion.py @@ -75,7 +75,7 @@ def test_get_downloader(printer, addon_downloader): def test_addon_info(printer, sample_addon_info): valid_minimum = {"name": "openpype_slack", "version": "1.0.0"} - assert AddonInfo(**valid_minimum), "Missing required fields" + assert AddonInfo.from_dict(valid_minimum), "Missing required fields" assert AddonInfo(name=valid_minimum["name"], version=valid_minimum["version"]), \ "Missing required fields" @@ -84,7 +84,7 @@ def test_addon_info(printer, sample_addon_info): # TODO should be probably implemented assert AddonInfo(valid_minimum), "Wrong argument format" - addon = AddonInfo(**sample_addon_info) + addon = AddonInfo.from_dict(sample_addon_info) assert addon, "Should be created" assert addon.name == "openpype_slack", "Incorrect name" assert addon.version == "1.0.0", "Incorrect version" @@ -95,10 +95,10 @@ def test_addon_info(printer, sample_addon_info): addon_as_dict = attr.asdict(addon) assert addon_as_dict["name"], "Dict approach should work" - with pytest.raises(AttributeError): + with pytest.raises(TypeError): # TODO should be probably implemented as . not dict first_source = addon.sources[0] - assert first_source.type == "http", "Not implemented" + assert first_source["type"] == "http", "Not implemented" def test_update_addon_state(printer, sample_addon_info, From df370e5d3cd321b3a34a37b7247ccf356ae9e053 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 23:43:09 +0200 Subject: [PATCH 401/513] Refactor to match with API changes of OpenPype --- openpype/hosts/fusion/api/lib.py | 8 ++++---- openpype/hosts/fusion/api/menu.py | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index db8dfb2795..19242da304 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -17,10 +17,10 @@ from openpype.pipeline import ( switch_container, legacy_io, ) +from openpype.pipeline.context_tools import get_current_project_asset + from .pipeline import get_current_comp, comp_lock_and_undo_chunk -from openpype.api import ( - get_asset -) + self = sys.modules[__name__] self._project = None @@ -70,7 +70,7 @@ def update_frame_range(start, end, comp=None, set_render_range=True, **kwargs): def set_framerange(): - asset_doc = get_asset() + asset_doc = get_current_project_asset() start = asset_doc["data"]["frameStart"] end = asset_doc["data"]["frameEnd"] diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 823670b9cf..c5eb093247 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -2,9 +2,7 @@ import sys from Qt import QtWidgets, QtCore -from avalon import api from openpype.tools.utils import host_tools - from openpype.style import load_stylesheet from openpype.lib import register_event_callback from openpype.hosts.fusion.scripts import ( @@ -14,6 +12,7 @@ from openpype.hosts.fusion.scripts import ( from openpype.hosts.fusion.api import ( set_framerange ) +from openpype.pipeline import legacy_io from .pulse import FusionPulse @@ -129,7 +128,7 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_task_changed(self): # Update current context label - label = api.Session["AVALON_ASSET"] + label = legacy_io.Session["AVALON_ASSET"] self.asset_label.setText(label) def register_callback(self, name, fn): From 618e6267b48b44dcc62615f33489e199a00a19bd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 23:43:42 +0200 Subject: [PATCH 402/513] Allow to minimize the menu so it doesn't always have to stay on top --- openpype/hosts/fusion/api/menu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index c5eb093247..bba94053a2 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -44,6 +44,7 @@ class OpenPypeMenu(QtWidgets.QWidget): QtCore.Qt.Window | QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint ) From 83fb00e0ffbbceb969aff2cf0865af08545784ca Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 23:54:28 +0200 Subject: [PATCH 403/513] Get start frame including handles --- .../fusion/plugins/load/load_sequence.py | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index abd0f4e411..faac942c53 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -149,9 +149,8 @@ class FusionLoadSequence(load.LoaderPlugin): tool["Clip"] = path # Set global in point to start frame (if in version.data) - start = context["version"]["data"].get("frameStart", None) - if start is not None: - loader_shift(tool, start, relative=False) + start = self._get_start(context["version"], tool) + loader_shift(tool, start, relative=False) imprint_container(tool, name=name, @@ -214,12 +213,7 @@ class FusionLoadSequence(load.LoaderPlugin): # Get start frame from version data project_name = legacy_io.active_project() version = get_version_by_id(project_name, representation["parent"]) - start = version["data"].get("frameStart") - if start is None: - self.log.warning("Missing start frame for updated version" - "assuming starts at frame 0 for: " - "{} ({})".format(tool.Name, representation)) - start = 0 + start = self._get_start(version, tool) with comp_lock_and_undo_chunk(comp, "Update Loader"): @@ -256,3 +250,27 @@ class FusionLoadSequence(load.LoaderPlugin): """Get first file in representation root""" files = sorted(os.listdir(root)) return os.path.join(root, files[0]) + + def _get_start(self, version_doc, tool): + """Return real start frame of published files (incl. handles)""" + data = version_doc["data"] + + # Get start frame directly with handle if it's in data + start = data.get("frameStartHandle") + if start is not None: + return start + + # Get frame start without handles + start = data.get("frameStart") + if start is None: + self.log.warning("Missing start frame for version " + "assuming starts at frame 0 for: " + "{}".format(tool.Name)) + return 0 + + # Use `handleStart` if the data is available + handle_start = data.get("handleStart") + if handle_start: + start -= handle_start + + return start From 0806534a278a6ae1a95f1f4f62ba17a86f89d5e0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 23:56:09 +0200 Subject: [PATCH 404/513] Return early if no need to shift --- openpype/hosts/fusion/plugins/load/load_sequence.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index faac942c53..1614704090 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -101,6 +101,9 @@ def loader_shift(loader, frame, relative=True): else: shift = frame - old_in + if not shift: + return + # Shifting global in will try to automatically compensate for the change # in the "ClipTimeStart" and "HoldFirstFrame" inputs, so we preserve those # input values to "just shift" the clip From 5058de28881aee71f8681034b4caa18f6b7b0605 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 23:56:46 +0200 Subject: [PATCH 405/513] Fix return value --- openpype/hosts/fusion/plugins/load/load_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index 1614704090..6f44c61d1b 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -102,7 +102,7 @@ def loader_shift(loader, frame, relative=True): shift = frame - old_in if not shift: - return + return 0 # Shifting global in will try to automatically compensate for the change # in the "ClipTimeStart" and "HoldFirstFrame" inputs, so we preserve those From df4d92d973c7d85561745dae167d744da6ac5e56 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 22:48:27 +0200 Subject: [PATCH 406/513] Hack in support for Fusion 18 (cherry picked from commit 5a75db9d7ba1f4df78e84f1315e8e8d9c9a357c0) --- openpype/hosts/fusion/hooks/pre_fusion_setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index c78d433e5c..ec5889a88a 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -33,6 +33,13 @@ class FusionPrelaunch(PreLaunchHook): self.log.info(f"Setting {py36_var}: '{py36_dir}'...") self.launch_context.env[py36_var] = py36_dir + # TODO: Set this for EITHER Fu16-17 OR Fu18+, don't do both + # Fusion 18+ does not look in FUSION16_PYTHON36_HOME anymore + # but instead uses FUSION_PYTHON3_HOME and requires the Python to + # be available on PATH to work. So let's enforce that for now. + self.launch_context.env["FUSION_PYTHON3_HOME"] = py36_dir + self.launch_context.env["PATH"] += ";" + py36_dir + # Add our Fusion Master Prefs which is the only way to customize # Fusion to define where it can read custom scripts and tools from self.log.info(f"Setting OPENPYPE_FUSION: {HOST_DIR}") From 0e4a73f6808c6c22a7ffa45a97589c7990473829 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 23:22:56 +0200 Subject: [PATCH 407/513] Fusion: Set OCIO project setting and set OCIO env var on launch (cherry picked from commit 8e9a7200d35ab7cba174855acfe03794936125cf) --- .../fusion/hooks/pre_fusion_ocio_hook.py | 40 +++++++++++++++++++ .../defaults/project_anatomy/imageio.json | 10 +++++ .../schemas/schema_anatomy_imageio.json | 29 ++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py diff --git a/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py b/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py new file mode 100644 index 0000000000..f7c7bc0b4c --- /dev/null +++ b/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py @@ -0,0 +1,40 @@ +import os +import platform + +from openpype.lib import PreLaunchHook, ApplicationLaunchFailed + + +class FusionPreLaunchOCIO(PreLaunchHook): + """Set OCIO environment variable for Fusion""" + app_groups = ["fusion"] + + def execute(self): + """Hook entry method.""" + + # get image io + project_anatomy = self.data["anatomy"] + + # make sure anatomy settings are having flame key + imageio_fusion = project_anatomy["imageio"].get("fusion") + if not imageio_fusion: + raise ApplicationLaunchFailed(( + "Anatomy project settings are missing `fusion` key. " + "Please make sure you remove project overrides on " + "Anatomy ImageIO") + ) + + ocio = imageio_fusion.get("ocio") + enabled = ocio.get("enabled", False) + if not enabled: + return + + platform_key = platform.system().lower() + ocio_path = ocio["configFilePath"][platform_key] + if not ocio_path: + raise ApplicationLaunchFailed( + "Fusion OCIO is enabled in project settings but no OCIO config" + f"path is set for your current platform: {platform_key}" + ) + + self.log.info(f"Setting OCIO config path: {ocio_path}") + self.launch_context.env["OCIO"] = os.pathsep.join(ocio_path) diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index f0be8f95f4..9b5e8639b1 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -236,6 +236,16 @@ "viewTransform": "sRGB gamma" } }, + "fusion": { + "ocio": { + "enabled": false, + "configFilePath": { + "windows": [], + "darwin": [], + "linux": [] + } + } + }, "flame": { "project": { "colourPolicy": "ACES 1.1", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json index ef8c907dda..644463fece 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -427,6 +427,35 @@ } ] }, + + { + "key": "fusion", + "type": "dict", + "label": "Fusion", + "children": [ + { + "key": "ocio", + "type": "dict", + "label": "OCIO", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Set OCIO variable for Fusion" + }, + { + "type": "path", + "key": "configFilePath", + "label": "OCIO Config File Path", + "multiplatform": true, + "multipath": true + } + ] + } + ] + }, { "key": "flame", "type": "dict", From cd40fab99ff2627bf29aa2c9de7a884d7640ff7e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 00:01:41 +0200 Subject: [PATCH 408/513] Add Fusion 18 application to defaults --- .../defaults/system_settings/applications.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 30b0a5cbe3..4a3e0c1b94 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -730,6 +730,21 @@ "OPENPYPE_LOG_NO_COLORS": "Yes" }, "variants": { + "18": { + "executables": { + "windows": [ + "C:\\Program Files\\Blackmagic Design\\Fusion 18\\Fusion.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, "17": { "executables": { "windows": [ From 839ded23bad51e9e949e960794baceaf4f4d9958 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 00:35:37 +0200 Subject: [PATCH 409/513] Make `handle_start` and `handle_end` more explicit arguments --- openpype/hosts/fusion/api/lib.py | 34 ++++++++++++++------------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 19242da304..dcf205ff6a 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -25,7 +25,8 @@ self = sys.modules[__name__] self._project = None -def update_frame_range(start, end, comp=None, set_render_range=True, **kwargs): +def update_frame_range(start, end, comp=None, set_render_range=True, + handle_start=0, handle_end=0): """Set Fusion comp's start and end frame range Args: @@ -34,7 +35,8 @@ def update_frame_range(start, end, comp=None, set_render_range=True, **kwargs): comp (object, Optional): comp object from fusion set_render_range (bool, Optional): When True this will also set the composition's render start and end frame. - kwargs (dict): additional kwargs + handle_start (float, int, Optional): frame handles before start frame + handle_end (float, int, Optional): frame handles after end frame Returns: None @@ -44,20 +46,15 @@ def update_frame_range(start, end, comp=None, set_render_range=True, **kwargs): if not comp: comp = get_current_comp() + # Convert any potential none type to zero + handle_start = handle_start or 0 + handle_end = handle_end or 0 + attrs = { - "COMPN_GlobalStart": start, - "COMPN_GlobalEnd": end + "COMPN_GlobalStart": start - handle_start, + "COMPN_GlobalEnd": end + handle_end } - # exclude handles if any found in kwargs - if kwargs.get("handle_start"): - handle_start = kwargs.get("handle_start") - attrs["COMPN_GlobalStart"] = int(start - handle_start) - - if kwargs.get("handle_end"): - handle_end = kwargs.get("handle_end") - attrs["COMPN_GlobalEnd"] = int(end + handle_end) - # set frame range if set_render_range: attrs.update({ @@ -73,12 +70,11 @@ def set_framerange(): asset_doc = get_current_project_asset() start = asset_doc["data"]["frameStart"] end = asset_doc["data"]["frameEnd"] - - data = { - "handle_start": asset_doc["data"]["handleStart"], - "handle_end": asset_doc["data"]["handleEnd"] - } - update_frame_range(start, end, set_render_range=True, **data) + handle_start = asset_doc["data"]["handleStart"], + handle_end = asset_doc["data"]["handleEnd"], + update_frame_range(start, end, set_render_range=True, + handle_start=handle_start, + handle_end=handle_end) def get_additional_data(container): From bb6c8817608c7e6e0c5a5bc40b1652361efb1651 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 11:15:25 +0200 Subject: [PATCH 410/513] OP-3940 - renamed sync_workfile_version to enabled --- .../hosts/photoshop/plugins/publish/collect_version.py | 10 +++------- .../settings/defaults/project_settings/photoshop.json | 2 +- .../projects_schema/schema_project_photoshop.json | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_version.py b/openpype/hosts/photoshop/plugins/publish/collect_version.py index 46f48b20fb..aff9f13bfb 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_version.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_version.py @@ -18,11 +18,7 @@ class CollectVersion(pyblish.api.InstancePlugin): hosts = ["photoshop"] families = ["image", "review"] - # controlled by Settings - sync_workfile_version = False - def process(self, instance): - if self.sync_workfile_version: - workfile_version = instance.context.data["version"] - self.log.debug(f"Applying version {workfile_version}") - instance.data["version"] = workfile_version + workfile_version = instance.context.data["version"] + self.log.debug(f"Applying version {workfile_version}") + instance.data["version"] = workfile_version diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 8ea36a3000..9d74df7cd5 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -16,7 +16,7 @@ "flatten_subset_template": "" }, "CollectVersion": { - "sync_workfile_version": true + "enabled": false }, "ValidateContainers": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 500c5d027b..e63e25d2c2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -143,8 +143,8 @@ }, { "type": "boolean", - "key": "sync_workfile_version", - "label": "Synchronize version with workfile" + "key": "enabled", + "label": "Enabled" } ] }, From 9c6d0b1d7e10a9d5d56832f8b0bb25952f25ad38 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 11:20:52 +0200 Subject: [PATCH 411/513] OP-3682 - changed to relative import --- common/openpype_common/distribution/addon_distribution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/openpype_common/distribution/addon_distribution.py b/common/openpype_common/distribution/addon_distribution.py index be6faab3e6..ad17a831d8 100644 --- a/common/openpype_common/distribution/addon_distribution.py +++ b/common/openpype_common/distribution/addon_distribution.py @@ -7,7 +7,7 @@ import requests import platform import shutil -from common.openpype_common.distribution.file_handler import RemoteFileHandler +from .file_handler import RemoteFileHandler class UrlType(Enum): From 52e1b563672d757a19b7ab0452f291189f10de6f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 11:47:05 +0200 Subject: [PATCH 412/513] OP-3952 - added text filter on project name to Tray Publisher --- openpype/tools/traypublisher/window.py | 13 +++++++++++++ openpype/tools/utils/models.py | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index cc33287091..d161afd37b 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -7,6 +7,7 @@ publishing plugins. """ from Qt import QtWidgets, QtCore +import qtawesome from openpype.pipeline import ( install_host, @@ -43,6 +44,7 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): projects_model = ProjectModel(dbcon) projects_proxy = ProjectSortFilterProxy() projects_proxy.setSourceModel(projects_model) + projects_proxy.setFilterKeyColumn(0) projects_view = QtWidgets.QListView(content_widget) projects_view.setObjectName("ChooseProjectView") @@ -59,10 +61,17 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): btns_layout.addWidget(cancel_btn, 0) btns_layout.addWidget(confirm_btn, 0) + txt_filter = QtWidgets.QLineEdit() + txt_filter.setPlaceholderText("Quick filter projects..") + txt_filter.setClearButtonEnabled(True) + txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), + QtWidgets.QLineEdit.LeadingPosition) + content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(0, 0, 0, 0) content_layout.setSpacing(20) content_layout.addWidget(header_label, 0) + content_layout.addWidget(txt_filter, 0) content_layout.addWidget(projects_view, 1) content_layout.addLayout(btns_layout, 0) @@ -79,11 +88,15 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): projects_view.doubleClicked.connect(self._on_double_click) confirm_btn.clicked.connect(self._on_confirm_click) cancel_btn.clicked.connect(self._on_cancel_click) + txt_filter.textChanged.connect( + lambda: projects_proxy.setFilterRegularExpression( + txt_filter.text())) self._projects_view = projects_view self._projects_model = projects_model self._cancel_btn = cancel_btn self._confirm_btn = confirm_btn + self._txt_filter = txt_filter self._publisher_window = publisher_window self._project_name = None diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 1faccef4dd..817d9c0944 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -356,10 +356,12 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): def filterAcceptsRow(self, source_row, source_parent): index = self.sourceModel().index(source_row, 0, source_parent) + string_pattern = self.filterRegularExpression().pattern() if self._filter_enabled: result = self._custom_index_filter(index) if result is not None: - return result + project_name = index.data(PROJECT_NAME_ROLE) + return string_pattern in project_name return super(ProjectSortFilterProxy, self).filterAcceptsRow( source_row, source_parent From ef33cda784b591e1faa899b9c4db9994146222fe Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 12:46:06 +0200 Subject: [PATCH 413/513] OP-3952 - used PlaceholderLineEdit Changed lambda to separate method as lamba is supposed to have some issue during QtDestroy --- openpype/tools/traypublisher/window.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index d161afd37b..6c17c66016 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -20,6 +20,7 @@ from openpype.tools.utils.models import ( ProjectModel, ProjectSortFilterProxy ) +from openpype.tools.utils import PlaceholderLineEdit class StandaloneOverlayWidget(QtWidgets.QFrame): @@ -61,7 +62,7 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): btns_layout.addWidget(cancel_btn, 0) btns_layout.addWidget(confirm_btn, 0) - txt_filter = QtWidgets.QLineEdit() + txt_filter = PlaceholderLineEdit(content_widget) txt_filter.setPlaceholderText("Quick filter projects..") txt_filter.setClearButtonEnabled(True) txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), @@ -88,12 +89,11 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): projects_view.doubleClicked.connect(self._on_double_click) confirm_btn.clicked.connect(self._on_confirm_click) cancel_btn.clicked.connect(self._on_cancel_click) - txt_filter.textChanged.connect( - lambda: projects_proxy.setFilterRegularExpression( - txt_filter.text())) + txt_filter.textChanged.connect(self._on_text_changed) self._projects_view = projects_view self._projects_model = projects_model + self._projects_proxy = projects_proxy self._cancel_btn = cancel_btn self._confirm_btn = confirm_btn self._txt_filter = txt_filter @@ -115,6 +115,10 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): def _on_cancel_click(self): self._set_project(self._project_name) + def _on_text_changed(self): + self._projects_proxy.setFilterRegularExpression( + self._txt_filter.text()) + def set_selected_project(self): index = self._projects_view.currentIndex() From eafae24e239f4130e3f6c4069b0d085218757614 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 16 Sep 2022 13:53:28 +0200 Subject: [PATCH 414/513] copy of workfile does not use 'copy' function but 'copyfile' --- openpype/tools/workfiles/files_widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 7377d10171..b7d31e4af4 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -578,7 +578,7 @@ class FilesWidget(QtWidgets.QWidget): src = self._get_selected_filepath() dst = os.path.join(self._workfiles_root, work_file) - shutil.copy(src, dst) + shutil.copyfile(src, dst) self.workfile_created.emit(dst) @@ -675,7 +675,7 @@ class FilesWidget(QtWidgets.QWidget): else: self.host.save_file(filepath) else: - shutil.copy(src_path, filepath) + shutil.copyfile(src_path, filepath) if isinstance(self.host, IWorkfileHost): self.host.open_workfile(filepath) else: From 210a3e05525913f7439b805c9671e38b7bae40db Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 14:19:07 +0200 Subject: [PATCH 415/513] OP-3953 - added persisting of last selected project in Tray Publisher Last selected project is stored in .json in app folder - eg. next to unzipped version zip. --- openpype/tools/traypublisher/window.py | 32 ++++++++++++++++++++++++++ openpype/tools/utils/models.py | 13 +++++++++++ 2 files changed, 45 insertions(+) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index cc33287091..2d32b5d6bf 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -19,6 +19,25 @@ from openpype.tools.utils.models import ( ProjectModel, ProjectSortFilterProxy ) +import appdirs +from openpype.lib import JSONSettingRegistry + + +class TrayPublisherRegistry(JSONSettingRegistry): + """Class handling OpenPype general settings registry. + + Attributes: + vendor (str): Name used for path construction. + product (str): Additional name used for path construction. + + """ + + def __init__(self): + self.vendor = "pypeclub" + self.product = "openpype" + name = "tray_publisher" + path = appdirs.user_data_dir(self.product, self.vendor) + super(TrayPublisherRegistry, self).__init__(name, path) class StandaloneOverlayWidget(QtWidgets.QFrame): @@ -90,6 +109,16 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): def showEvent(self, event): self._projects_model.refresh() + + setting_registry = TrayPublisherRegistry() + project_name = setting_registry.get_item("project_name") + if project_name: + index = self._projects_model.get_index(project_name) + if index: + mode = QtCore.QItemSelectionModel.Select | \ + QtCore.QItemSelectionModel.Rows + self._projects_view.selectionModel().select(index, mode) + self._cancel_btn.setVisible(self._project_name is not None) super(StandaloneOverlayWidget, self).showEvent(event) @@ -119,6 +148,9 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): self.setVisible(False) self.project_selected.emit(project_name) + setting_registry = TrayPublisherRegistry() + setting_registry.set_item("project_name", project_name) + class TrayPublishWindow(PublisherWindow): def __init__(self, *args, **kwargs): diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 1faccef4dd..2d917fcc49 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -330,6 +330,19 @@ class ProjectModel(QtGui.QStandardItemModel): if new_items: root_item.appendRows(new_items) + def get_index(self, project_name): + """ + Get index of 'project_name' value. + + Args: + project_name (str): + Returns: + (QModelIndex) + """ + val = self._items_by_name.get(project_name) + if val: + return self.indexFromItem(val) + class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): def __init__(self, *args, **kwargs): From f24925dfd28fe0e053fcdb25239341d6056b863e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 16 Sep 2022 14:21:52 +0200 Subject: [PATCH 416/513] resaved default settings to add missing values --- .../defaults/project_settings/blender.json | 32 +++++++++---------- .../defaults/project_settings/houdini.json | 3 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 2720e0286d..7acecfaae0 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -36,35 +36,35 @@ "layout" ] }, - "ExtractBlendAnimation": { - "enabled": true, - "optional": true, - "active": true - }, - "ExtractCamera": { - "enabled": true, - "optional": true, - "active": true - }, "ExtractFBX": { "enabled": true, "optional": true, "active": false }, - "ExtractAnimationFBX": { - "enabled": true, - "optional": true, - "active": false - }, "ExtractABC": { "enabled": true, "optional": true, "active": false }, + "ExtractBlendAnimation": { + "enabled": true, + "optional": true, + "active": true + }, + "ExtractAnimationFBX": { + "enabled": true, + "optional": true, + "active": false + }, + "ExtractCamera": { + "enabled": true, + "optional": true, + "active": true + }, "ExtractLayout": { "enabled": true, "optional": true, "active": false } } -} +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index af0789ff8a..cdf829db57 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -6,7 +6,8 @@ "windows": "", "darwin": "", "linux": "" - } + }, + "shelf_definition": [] } ], "create": { From 7331fd20691ba2a7047d98e7baa0e7c04ce750e6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 14:28:04 +0200 Subject: [PATCH 417/513] formatting change Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/traypublisher/window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 2d32b5d6bf..56c5594638 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -115,8 +115,9 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): if project_name: index = self._projects_model.get_index(project_name) if index: - mode = QtCore.QItemSelectionModel.Select | \ - QtCore.QItemSelectionModel.Rows + mode = ( + QtCore.QItemSelectionModel.Select + | QtCore.QItemSelectionModel.Rows) self._projects_view.selectionModel().select(index, mode) self._cancel_btn.setVisible(self._project_name is not None) From 563515c0e63f41dbb2d2c01a7518017bf88e7602 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 20:56:12 +0800 Subject: [PATCH 418/513] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 50a2f2112a..5a317f4b53 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -1,5 +1,9 @@ import pyblish.api - +from openpype.pipeline.workfile.lock_workfile import( + is_workfile_lock_enabled, + remove_workfile_lock +) +from openpype.pipeline import legacy_io class SaveCurrentScene(pyblish.api.ContextPlugin): """Save current scene @@ -23,5 +27,8 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): "are no modifications..") return + active_project = legacy_io.active_project() + if is_workfile_lock_enabled("maya", active_project): + remove_workfile_lock(current) self.log.info("Saving current file..") cmds.file(save=True, force=True) From c2d9ef859e7fbaa697e32ef869c69365e06b9e85 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 21:00:03 +0800 Subject: [PATCH 419/513] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 5a317f4b53..99d486e545 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -1,4 +1,5 @@ import pyblish.api + from openpype.pipeline.workfile.lock_workfile import( is_workfile_lock_enabled, remove_workfile_lock @@ -28,6 +29,7 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): return active_project = legacy_io.active_project() + # remove lockfile before saving if is_workfile_lock_enabled("maya", active_project): remove_workfile_lock(current) self.log.info("Saving current file..") From 1049f22c4d5f43de8a5c81363ecf77ed10728baf Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 21:01:45 +0800 Subject: [PATCH 420/513] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 99d486e545..33a297889a 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -1,6 +1,5 @@ import pyblish.api - -from openpype.pipeline.workfile.lock_workfile import( +from openpype.pipeline.workfile.lock_workfile import ( is_workfile_lock_enabled, remove_workfile_lock ) From 8fab407da2e203d8815d8c2005cd84285f316737 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 21:12:22 +0800 Subject: [PATCH 421/513] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 33a297889a..15472dba96 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -26,10 +26,10 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): self.log.debug("Skipping file save as there " "are no modifications..") return - - active_project = legacy_io.active_project() + project_name = context.data["projectName"] + project_settings = context.data["project_settings"] # remove lockfile before saving - if is_workfile_lock_enabled("maya", active_project): + if is_workfile_lock_enabled("maya", project_name, project_settings): remove_workfile_lock(current) self.log.info("Saving current file..") cmds.file(save=True, force=True) From 42ad74b398c21aee19f5fa4d7f7710aaf4c4673d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 21:13:14 +0800 Subject: [PATCH 422/513] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 15472dba96..45e62e7b44 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -3,7 +3,7 @@ from openpype.pipeline.workfile.lock_workfile import ( is_workfile_lock_enabled, remove_workfile_lock ) -from openpype.pipeline import legacy_io + class SaveCurrentScene(pyblish.api.ContextPlugin): """Save current scene From 508b17963d09bda307051d2483fd83753ca080b1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 15:41:16 +0200 Subject: [PATCH 423/513] Move Fusion OCIO settings out of anatomy into project settings --- .../defaults/project_anatomy/imageio.json | 10 ----- .../defaults/project_settings/fusion.json | 12 ++++++ .../schemas/projects_schema/schema_main.json | 4 ++ .../schema_project_fusion.json | 38 +++++++++++++++++++ .../schemas/schema_anatomy_imageio.json | 29 -------------- 5 files changed, 54 insertions(+), 39 deletions(-) create mode 100644 openpype/settings/defaults/project_settings/fusion.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json diff --git a/openpype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json index 9b5e8639b1..f0be8f95f4 100644 --- a/openpype/settings/defaults/project_anatomy/imageio.json +++ b/openpype/settings/defaults/project_anatomy/imageio.json @@ -236,16 +236,6 @@ "viewTransform": "sRGB gamma" } }, - "fusion": { - "ocio": { - "enabled": false, - "configFilePath": { - "windows": [], - "darwin": [], - "linux": [] - } - } - }, "flame": { "project": { "colourPolicy": "ACES 1.1", diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json new file mode 100644 index 0000000000..1b4c4c55b5 --- /dev/null +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -0,0 +1,12 @@ +{ + "imageio": { + "ocio": { + "enabled": false, + "configFilePath": { + "windows": [], + "darwin": [], + "linux": [] + } + } + } +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 80b1baad1b..0b9fbf7470 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -90,6 +90,10 @@ "type": "schema", "name": "schema_project_nuke" }, + { + "type": "schema", + "name": "schema_project_fusion" + }, { "type": "schema", "name": "schema_project_hiero" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json new file mode 100644 index 0000000000..8f98a8173f --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -0,0 +1,38 @@ +{ + "type": "dict", + "collapsible": true, + "key": "fusion", + "label": "Fusion", + "is_file": true, + "children": [ + { + "key": "imageio", + "type": "dict", + "label": "Color Management (ImageIO)", + "collapsible": true, + "children": [ + { + "key": "ocio", + "type": "dict", + "label": "OpenColorIO (OCIO)", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Set OCIO variable for Fusion" + }, + { + "type": "path", + "key": "configFilePath", + "label": "OCIO Config File Path", + "multiplatform": true, + "multipath": true + } + ] + } + ] + } + ] +} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json index 644463fece..ef8c907dda 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -427,35 +427,6 @@ } ] }, - - { - "key": "fusion", - "type": "dict", - "label": "Fusion", - "children": [ - { - "key": "ocio", - "type": "dict", - "label": "OCIO", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Set OCIO variable for Fusion" - }, - { - "type": "path", - "key": "configFilePath", - "label": "OCIO Config File Path", - "multiplatform": true, - "multipath": true - } - ] - } - ] - }, { "key": "flame", "type": "dict", From 7aa905898e6726e910c05be8db6514e5e656b8c1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 15:43:11 +0200 Subject: [PATCH 424/513] Use new settings location --- openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py b/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py index f7c7bc0b4c..12fc640f5c 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py @@ -12,10 +12,10 @@ class FusionPreLaunchOCIO(PreLaunchHook): """Hook entry method.""" # get image io - project_anatomy = self.data["anatomy"] + project_settings = self.data["project_settings"] # make sure anatomy settings are having flame key - imageio_fusion = project_anatomy["imageio"].get("fusion") + imageio_fusion = project_settings.get("fusion", {}).get("imageio") if not imageio_fusion: raise ApplicationLaunchFailed(( "Anatomy project settings are missing `fusion` key. " From 3161d3d8debb64bdd3c5705d7d4ab0c0c0a70cdc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 16 Sep 2022 19:13:31 +0200 Subject: [PATCH 425/513] use explicit float conversions for decimal calculations --- openpype/widgets/nice_checkbox.py | 48 ++++++++++++++++--------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/openpype/widgets/nice_checkbox.py b/openpype/widgets/nice_checkbox.py index ccd079c0fb..56e6d2ac24 100644 --- a/openpype/widgets/nice_checkbox.py +++ b/openpype/widgets/nice_checkbox.py @@ -111,14 +111,14 @@ class NiceCheckbox(QtWidgets.QFrame): return QtCore.QSize(width, height) def get_width_hint_by_height(self, height): - return ( - height / self._base_size.height() - ) * self._base_size.width() + return int(( + float(height) / self._base_size.height() + ) * self._base_size.width()) def get_height_hint_by_width(self, width): - return ( - width / self._base_size.width() - ) * self._base_size.height() + return int(( + float(width) / self._base_size.width() + ) * self._base_size.height()) def setFixedHeight(self, *args, **kwargs): self._fixed_height_set = True @@ -321,7 +321,7 @@ class NiceCheckbox(QtWidgets.QFrame): bg_color = self.unchecked_bg_color else: - offset_ratio = self._current_step / self._steps + offset_ratio = float(self._current_step) / self._steps # Animation bg bg_color = self.steped_color( self.checked_bg_color, @@ -332,7 +332,8 @@ class NiceCheckbox(QtWidgets.QFrame): margins_ratio = self._checker_margins_divider if margins_ratio > 0: size_without_margins = int( - (frame_rect.height() / margins_ratio) * (margins_ratio - 2) + (float(frame_rect.height()) / margins_ratio) + * (margins_ratio - 2) ) size_without_margins -= size_without_margins % 2 margin_size_c = ceil( @@ -434,21 +435,21 @@ class NiceCheckbox(QtWidgets.QFrame): def _get_enabled_icon_path( self, painter, checker_rect, step=None, half_steps=None ): - fifteenth = checker_rect.height() / 15 + fifteenth = float(checker_rect.height()) / 15 # Left point p1 = QtCore.QPoint( - checker_rect.x() + (5 * fifteenth), - checker_rect.y() + (9 * fifteenth) + int(checker_rect.x() + (5 * fifteenth)), + int(checker_rect.y() + (9 * fifteenth)) ) # Middle bottom point p2 = QtCore.QPoint( checker_rect.center().x(), - checker_rect.y() + (11 * fifteenth) + int(checker_rect.y() + (11 * fifteenth)) ) # Top right point p3 = QtCore.QPoint( - checker_rect.x() + (10 * fifteenth), - checker_rect.y() + (5 * fifteenth) + int(checker_rect.x() + (10 * fifteenth)), + int(checker_rect.y() + (5 * fifteenth)) ) if step is not None: multiplier = (half_steps - step) @@ -458,16 +459,16 @@ class NiceCheckbox(QtWidgets.QFrame): p3c = p3 - checker_rect.center() p1o = QtCore.QPoint( - (p1c.x() / half_steps) * multiplier, - (p1c.y() / half_steps) * multiplier + int((float(p1c.x()) / half_steps) * multiplier), + int((float(p1c.y()) / half_steps) * multiplier) ) p2o = QtCore.QPoint( - (p2c.x() / half_steps) * multiplier, - (p2c.y() / half_steps) * multiplier + int((float(p2c.x()) / half_steps) * multiplier), + int((float(p2c.y()) / half_steps) * multiplier) ) p3o = QtCore.QPoint( - (p3c.x() / half_steps) * multiplier, - (p3c.y() / half_steps) * multiplier + int((float(p3c.x()) / half_steps) * multiplier), + int((float(p3c.y()) / half_steps) * multiplier) ) p1 -= p1o @@ -484,11 +485,12 @@ class NiceCheckbox(QtWidgets.QFrame): self, painter, checker_rect, step=None, half_steps=None ): center_point = QtCore.QPointF( - checker_rect.width() / 2, checker_rect.height() / 2 + float(checker_rect.width()) / 2, + float(checker_rect.height()) / 2 ) - offset = ( + offset = float(( (center_point + QtCore.QPointF(0, 0)) / 2 - ).x() / 4 * 5 + ).x()) / 4 * 5 if step is not None: diff = center_point.x() - offset diff_offset = (diff / half_steps) * (half_steps - step) From d1cc57b2e5aff77147ad8d9ba54e7bb3d4fe4b64 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 17 Sep 2022 04:11:42 +0000 Subject: [PATCH 426/513] [Automated] Bump version --- CHANGELOG.md | 45 +++++++++++++++++++++++---------------------- openpype/version.py | 2 +- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6b620d58..af347cadfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,39 @@ # Changelog -## [3.14.3-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.3-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.2...HEAD) **🚀 Enhancements** +- Github issues adding `running version` section [\#3864](https://github.com/pypeclub/OpenPype/pull/3864) +- Publisher: Increase size of main window [\#3862](https://github.com/pypeclub/OpenPype/pull/3862) +- Houdini: Increment current file on workfile publish [\#3840](https://github.com/pypeclub/OpenPype/pull/3840) - Publisher: Add new publisher to host tools [\#3833](https://github.com/pypeclub/OpenPype/pull/3833) +- General: lock task workfiles when they are working on [\#3810](https://github.com/pypeclub/OpenPype/pull/3810) - Maya: Workspace mel loaded from settings [\#3790](https://github.com/pypeclub/OpenPype/pull/3790) **🐛 Bug fixes** +- Settings: Add missing default settings [\#3870](https://github.com/pypeclub/OpenPype/pull/3870) +- General: Copy of workfile does not use 'copy' function but 'copyfile' [\#3869](https://github.com/pypeclub/OpenPype/pull/3869) +- Tray Publisher: skip plugin if otioTimeline is missing [\#3856](https://github.com/pypeclub/OpenPype/pull/3856) +- Maya: Extract Playblast fix textures + labelize viewport show settings [\#3852](https://github.com/pypeclub/OpenPype/pull/3852) - Ftrack: Url validation does not require ftrackapp [\#3834](https://github.com/pypeclub/OpenPype/pull/3834) - Maya+Ftrack: Change typo in family name `mayaascii` -\> `mayaAscii` [\#3820](https://github.com/pypeclub/OpenPype/pull/3820) +- Maya Deadline: Fix Tile Rendering by forcing integer pixel values [\#3758](https://github.com/pypeclub/OpenPype/pull/3758) + +**🔀 Refactored code** + +- Hiero: Use new Extractor location [\#3851](https://github.com/pypeclub/OpenPype/pull/3851) +- Maya: Remove old legacy \(ftrack\) plug-ins that are of no use anymore [\#3819](https://github.com/pypeclub/OpenPype/pull/3819) +- Nuke: Use new Extractor location [\#3799](https://github.com/pypeclub/OpenPype/pull/3799) +- Maya: Use new Extractor location [\#3775](https://github.com/pypeclub/OpenPype/pull/3775) +- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) + +**Merged pull requests:** + +- Remove lockfile during publish [\#3874](https://github.com/pypeclub/OpenPype/pull/3874) ## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) @@ -21,7 +42,6 @@ **🆕 New features** - Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) -- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) **🚀 Enhancements** @@ -57,12 +77,10 @@ - General: Move create project folders to pipeline [\#3768](https://github.com/pypeclub/OpenPype/pull/3768) - General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) - Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) -- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) - General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) - Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) -- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) - Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) **Merged pull requests:** @@ -74,17 +92,10 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.1-nightly.4...3.14.1) -### 📖 Documentation - -- Documentation: Few updates [\#3698](https://github.com/pypeclub/OpenPype/pull/3698) - **🚀 Enhancements** - General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) - Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) -- General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) -- Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) -- Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) **🐛 Bug fixes** @@ -94,10 +105,6 @@ - Nuke: missing job dependency if multiple bake streams [\#3737](https://github.com/pypeclub/OpenPype/pull/3737) - Nuke: color-space settings from anatomy is working [\#3721](https://github.com/pypeclub/OpenPype/pull/3721) - Settings: Fix studio default anatomy save [\#3716](https://github.com/pypeclub/OpenPype/pull/3716) -- Maya: Use project name instead of project code [\#3709](https://github.com/pypeclub/OpenPype/pull/3709) -- Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) -- Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) -- PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) **🔀 Refactored code** @@ -106,6 +113,7 @@ - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) +- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) - AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) - Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) @@ -114,17 +122,10 @@ - Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) - General: Move subset name functionality [\#3723](https://github.com/pypeclub/OpenPype/pull/3723) - General: Move creators plugin getter [\#3714](https://github.com/pypeclub/OpenPype/pull/3714) -- General: Move constants from lib to client [\#3713](https://github.com/pypeclub/OpenPype/pull/3713) -- Loader: Subset groups using client operations [\#3710](https://github.com/pypeclub/OpenPype/pull/3710) -- TVPaint: Defined as module [\#3707](https://github.com/pypeclub/OpenPype/pull/3707) -- StandalonePublisher: Define StandalonePublisher as module [\#3706](https://github.com/pypeclub/OpenPype/pull/3706) -- TrayPublisher: Define TrayPublisher as module [\#3705](https://github.com/pypeclub/OpenPype/pull/3705) -- General: Move context specific functions to context tools [\#3702](https://github.com/pypeclub/OpenPype/pull/3702) **Merged pull requests:** - Hiero: Define hiero as module [\#3717](https://github.com/pypeclub/OpenPype/pull/3717) -- Deadline: better logging for DL webservice failures [\#3694](https://github.com/pypeclub/OpenPype/pull/3694) ## [3.14.0](https://github.com/pypeclub/OpenPype/tree/3.14.0) (2022-08-18) diff --git a/openpype/version.py b/openpype/version.py index e8a65b04d2..a2335b696b 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.3-nightly.1" +__version__ = "3.14.3-nightly.2" From ad4211656b7651b7eef42351aa13240add36a109 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 10:52:36 +0200 Subject: [PATCH 427/513] Remove double setting of additional attributes for Arnold --- openpype/hosts/maya/api/lib_rendersettings.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 7cd2193086..67b66b8024 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -133,20 +133,7 @@ class RenderSettings(object): cmds.setAttr( "defaultArnoldDriver.mergeAOVs", multi_exr) - # Passes additional options in from the schema as a list - # but converts it to a dictionary because ftrack doesn't - # allow fullstops in custom attributes. Then checks for - # type of MtoA attribute passed to adjust the `setAttr` - # command accordingly. self._additional_attribs_setter(additional_options) - for item in additional_options: - attribute, value = item - if (cmds.getAttr(str(attribute), type=True)) == "long": - cmds.setAttr(str(attribute), int(value)) - elif (cmds.getAttr(str(attribute), type=True)) == "bool": - cmds.setAttr(str(attribute), int(value), type = "Boolean") # noqa - elif (cmds.getAttr(str(attribute), type=True)) == "string": - cmds.setAttr(str(attribute), str(value), type = "string") # noqa reset_frame_range() def _set_redshift_settings(self, width, height): From 2c8eaec2d7c93e53accfec87d828bf8648d38428 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 11:04:12 +0200 Subject: [PATCH 428/513] Remove debug print statement --- openpype/hosts/maya/api/lib_rendersettings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 67b66b8024..a62145e921 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -217,7 +217,6 @@ class RenderSettings(object): cmds.setAttr("defaultRenderGlobals.extensionPadding", 4) def _additional_attribs_setter(self, additional_attribs): - print(additional_attribs) for item in additional_attribs: attribute, value = item if (cmds.getAttr(str(attribute), type=True)) == "long": From 0c71ec1d3840db418a4077efa1eddc57436f4d27 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 13:18:04 +0200 Subject: [PATCH 429/513] Tweak readability, log error on unsupported attribute type --- openpype/hosts/maya/api/lib_rendersettings.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index a62145e921..0618420b2b 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -5,6 +5,7 @@ import maya.mel as mel import six import sys +from openpype.lib import Logger from openpype.api import ( get_project_settings, get_current_project_settings @@ -38,6 +39,8 @@ class RenderSettings(object): "underscore": "_" } + log = Logger.get_logger("RenderSettings") + @classmethod def get_image_prefix_attr(cls, renderer): return cls._image_prefix_nodes[renderer] @@ -219,9 +222,16 @@ class RenderSettings(object): def _additional_attribs_setter(self, additional_attribs): for item in additional_attribs: attribute, value = item - if (cmds.getAttr(str(attribute), type=True)) == "long": - cmds.setAttr(str(attribute), int(value)) - elif (cmds.getAttr(str(attribute), type=True)) == "bool": - cmds.setAttr(str(attribute), int(value)) # noqa - elif (cmds.getAttr(str(attribute), type=True)) == "string": - cmds.setAttr(str(attribute), str(value), type = "string") # noqa + attribute = str(attribute) # ensure str conversion from settings + attribute_type = cmds.getAttr(attribute, type=True) + if attribute_type in {"long", "bool"}: + cmds.setAttr(attribute, int(value)) + elif attribute_type == "string": + cmds.setAttr(attribute, str(value), type="string") + else: + self.log.error( + "Attribute {attribute} can not be set due to unsupported " + "type: {attribute_type}".format( + attribute=attribute, + attribute_type=attribute_type) + ) From 83922a87cd08630b549cb6f0220aaacfcb5623ec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 13:19:37 +0200 Subject: [PATCH 430/513] Add double attribute support (float) --- openpype/hosts/maya/api/lib_rendersettings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 0618420b2b..777a6ffbc9 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -228,6 +228,8 @@ class RenderSettings(object): cmds.setAttr(attribute, int(value)) elif attribute_type == "string": cmds.setAttr(attribute, str(value), type="string") + elif attribute_type in {"double", "doubleAngle", "doubleLinear"}: + cmds.setAttr(attribute, float(value)) else: self.log.error( "Attribute {attribute} can not be set due to unsupported " From b36e1ded7d24c74b0601d0c65567f4674d6bf212 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 13:29:38 +0200 Subject: [PATCH 431/513] Set default image format for V-Ray+Redshift to exr instead of png and iff --- openpype/settings/defaults/project_settings/maya.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 8643297f02..76ef0a7338 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -49,7 +49,7 @@ "vray_renderer": { "image_prefix": "maya///", "engine": "1", - "image_format": "png", + "image_format": "exr", "aov_list": [], "additional_options": [] }, @@ -57,7 +57,7 @@ "image_prefix": "maya///", "primary_gi_engine": "0", "secondary_gi_engine": "0", - "image_format": "iff", + "image_format": "exr", "multilayer_exr": true, "force_combine": true, "aov_list": [], From b48ad01e4c6ada98a96f048d93615fa072190b86 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 10:48:44 +0200 Subject: [PATCH 432/513] Changed function name Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/utils/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 2d917fcc49..d072ff297d 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -330,7 +330,7 @@ class ProjectModel(QtGui.QStandardItemModel): if new_items: root_item.appendRows(new_items) - def get_index(self, project_name): + def find_project(self, project_name): """ Get index of 'project_name' value. From ae7f7ebabbd69352fab23f7bc9757c90c80881b5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 10:54:23 +0200 Subject: [PATCH 433/513] OP-3953 - added missing mapping to proxy model --- openpype/tools/traypublisher/window.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 56c5594638..0c99a55998 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -113,7 +113,10 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): setting_registry = TrayPublisherRegistry() project_name = setting_registry.get_item("project_name") if project_name: - index = self._projects_model.get_index(project_name) + index = None + src_index = self._projects_model.find_project(project_name) + if src_index is not None: + index = self._projects_proxy.mapFromSource(src_index) if index: mode = ( QtCore.QItemSelectionModel.Select From 94aa11f1167e90719ef9426bfae78492b7491366 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 10:57:47 +0200 Subject: [PATCH 434/513] OP-3953 - added missing proxy model variable --- openpype/tools/traypublisher/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 0c99a55998..ca8c0758d6 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -101,6 +101,7 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): self._projects_view = projects_view self._projects_model = projects_model + self._projects_proxy = projects_proxy self._cancel_btn = cancel_btn self._confirm_btn = confirm_btn From f711b529b9693d73d08d696acba077bf45ba3f77 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:01:52 +0200 Subject: [PATCH 435/513] OP-3952 - safer resolving of search pattern --- openpype/tools/utils/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 817d9c0944..6663ca1b0a 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -361,7 +361,9 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): result = self._custom_index_filter(index) if result is not None: project_name = index.data(PROJECT_NAME_ROLE) - return string_pattern in project_name + if project_name is None: + return result + return string_pattern.lower() in project_name.lower() return super(ProjectSortFilterProxy, self).filterAcceptsRow( source_row, source_parent From 31beb5b6b15a4c7b110c21ebafc71eb3163b004b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:03:15 +0200 Subject: [PATCH 436/513] OP-3952 - sort projects after refresh --- openpype/tools/traypublisher/window.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 6c17c66016..b134e8ab86 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -103,6 +103,8 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): def showEvent(self, event): self._projects_model.refresh() + # Sort projects after refresh + self._projects_proxy.sort(0) self._cancel_btn.setVisible(self._project_name is not None) super(StandaloneOverlayWidget, self).showEvent(event) From a5b2d8c6bd8a6c6ee6ed636deb3d823d9103aabb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:04:47 +0200 Subject: [PATCH 437/513] OP-3952 - set sorting as case insensitive --- openpype/tools/utils/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 6663ca1b0a..f31a56b2f4 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -335,6 +335,9 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): def __init__(self, *args, **kwargs): super(ProjectSortFilterProxy, self).__init__(*args, **kwargs) self._filter_enabled = True + # Disable case sensitivity + self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + self._filter_enabled = True def lessThan(self, left_index, right_index): if left_index.data(PROJECT_NAME_ROLE) is None: From 222c64b0c68f6dbde3ac8b6bdf33db51732449e4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:45:58 +0200 Subject: [PATCH 438/513] OP-3953 - handle missing registry json file --- openpype/tools/traypublisher/window.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index ca8c0758d6..f3ed01b151 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -112,7 +112,11 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): self._projects_model.refresh() setting_registry = TrayPublisherRegistry() - project_name = setting_registry.get_item("project_name") + try: + project_name = setting_registry.get_item("project_name") + except ValueError: + project_name = None + if project_name: index = None src_index = self._projects_model.find_project(project_name) From 091498e13017cc96d0638f34fb03b0f0caee59e8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:57:29 +0200 Subject: [PATCH 439/513] OP-3952 - remove duplicated field Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/utils/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index f31a56b2f4..cff5238a69 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -337,7 +337,6 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): self._filter_enabled = True # Disable case sensitivity self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) - self._filter_enabled = True def lessThan(self, left_index, right_index): if left_index.data(PROJECT_NAME_ROLE) is None: From 8dc0f6f011c17c455f62269768fe0c728f76631b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 19 Sep 2022 13:15:27 +0200 Subject: [PATCH 440/513] change return value of 'rename_filepaths_by_frame_start' when frame start is same as mark in --- openpype/hosts/tvpaint/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/lib.py b/openpype/hosts/tvpaint/lib.py index c67ab1e4fb..bf47e725cb 100644 --- a/openpype/hosts/tvpaint/lib.py +++ b/openpype/hosts/tvpaint/lib.py @@ -648,7 +648,7 @@ def rename_filepaths_by_frame_start( """Change frames in filenames of finished images to new frame start.""" # Skip if source first frame is same as destination first frame if range_start == new_frame_start: - return + return {} # Calculate frame end new_frame_end = range_end + (new_frame_start - range_start) From 2dba044e48c5670fffe0562754dc108212d2e193 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 13:49:01 +0200 Subject: [PATCH 441/513] OP-3943 - added publish field to Settings for PS CollectReview Customer wants to configure if review should be published by default for Photoshop. --- .../defaults/project_settings/photoshop.json | 3 +++ .../projects_schema/schema_project_photoshop.json | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 552c2c9cad..3477d185a6 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -15,6 +15,9 @@ "CollectInstances": { "flatten_subset_template": "" }, + "CollectReview": { + "publish": true + }, "ValidateContainers": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 7aa49c99a4..7294ba8608 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -131,6 +131,19 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CollectReview", + "label": "Collect Review", + "children": [ + { + "type": "boolean", + "key": "publish", + "label": "Publish review" + } + ] + }, { "type": "schema_template", "name": "template_publish_plugin", From 0d4549f7bf5af630eb1a6072471f0cd543e52ddb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 13:50:07 +0200 Subject: [PATCH 442/513] OP-3943 - added publish field to PS CollectReview Customer wants to configure if review should be published by default for Photoshop. Default could be set in Settings, artist might decide to override it for particular publish. --- openpype/hosts/photoshop/plugins/publish/collect_review.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_review.py b/openpype/hosts/photoshop/plugins/publish/collect_review.py index 7f395b46d7..7e598a8250 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_review.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_review.py @@ -25,6 +25,8 @@ class CollectReview(pyblish.api.ContextPlugin): hosts = ["photoshop"] order = pyblish.api.CollectorOrder + 0.1 + publish = True + def process(self, context): family = "review" subset = get_subset_name( @@ -45,5 +47,6 @@ class CollectReview(pyblish.api.ContextPlugin): "family": family, "families": [], "representations": [], - "asset": os.environ["AVALON_ASSET"] + "asset": os.environ["AVALON_ASSET"], + "publish": self.publish }) From bd3f6acb0275ee90b3107f8ccdf4b9e8a6e2770b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 14:35:44 +0200 Subject: [PATCH 443/513] Refactor `HOST_DIR` to `FUSION_HOST_DIR` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- openpype/hosts/fusion/hooks/pre_fusion_setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index ec5889a88a..a0796de10a 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -1,6 +1,6 @@ import os from openpype.lib import PreLaunchHook, ApplicationLaunchFailed -from openpype.hosts.fusion import HOST_DIR +from openpype.hosts.fusion import FUSION_HOST_DIR class FusionPrelaunch(PreLaunchHook): @@ -42,8 +42,8 @@ class FusionPrelaunch(PreLaunchHook): # Add our Fusion Master Prefs which is the only way to customize # Fusion to define where it can read custom scripts and tools from - self.log.info(f"Setting OPENPYPE_FUSION: {HOST_DIR}") - self.launch_context.env["OPENPYPE_FUSION"] = HOST_DIR + self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") + self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR pref_var = "FUSION16_MasterPrefs" # used by both Fu16 and Fu17 prefs = os.path.join(HOST_DIR, "deploy", "fusion_shared.prefs") From daf29122e6f7be1b6cc4d3171423dc61764d726d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 14:36:36 +0200 Subject: [PATCH 444/513] Refactor `HOST_DIR` to `FUSION_HOST_DIR` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- openpype/hosts/fusion/hooks/pre_fusion_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index a0796de10a..0ba7e92bb1 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -46,6 +46,6 @@ class FusionPrelaunch(PreLaunchHook): self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR pref_var = "FUSION16_MasterPrefs" # used by both Fu16 and Fu17 - prefs = os.path.join(HOST_DIR, "deploy", "fusion_shared.prefs") + prefs = os.path.join(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") self.log.info(f"Setting {pref_var}: {prefs}") self.launch_context.env[pref_var] = prefs From 5694cff114a3f1f3a42461d72bb03cff9521bb58 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 14:47:45 +0200 Subject: [PATCH 445/513] Use `FUSION_PYTHON3_HOME` instead of `FUSION16_PYTHON36_HOME` as input variable --- .../hosts/fusion/hooks/pre_fusion_setup.py | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index 0ba7e92bb1..d043d54322 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -4,48 +4,58 @@ from openpype.hosts.fusion import FUSION_HOST_DIR class FusionPrelaunch(PreLaunchHook): - """ - This hook will check if current workfile path has Fusion - project inside. + """Prepares OpenPype Fusion environment + + Requires FUSION_PYTHON3_HOME to be defined in the environment for Fusion + to point at a valid Python 3 build for Fusion. That is Python 3.3-3.10 + for Fusion 18 and Fusion 3.6 for Fusion 16 and 17. + + This also sets FUSION16_MasterPrefs to apply the fusion master prefs + as set in openpype/hosts/fusion/deploy/fusion_shared.prefs to enable + the OpenPype menu and force Python 3 over Python 2. + """ app_groups = ["fusion"] def execute(self): - # making sure python 3.6 is installed at provided path - py36_var = "FUSION16_PYTHON36_HOME" - fusion_python36_home = self.launch_context.env.get(py36_var, "") + # making sure python 3 is installed at provided path + # Py 3.3-3.10 for Fusion 18+ or Py 3.6 for Fu 16-17 + py3_var = "FUSION_PYTHON3_HOME" + fusion_python3_home = self.launch_context.env.get(py3_var, "") - self.log.info(f"Looking for Python 3.6 in: {fusion_python36_home}") - for path in fusion_python36_home.split(os.pathsep): + self.log.info(f"Looking for Python 3 in: {fusion_python3_home}") + for path in fusion_python3_home.split(os.pathsep): # Allow defining multiple paths to allow "fallback" to other # path. But make to set only a single path as final variable. - py36_dir = os.path.normpath(path) - if os.path.isdir(py36_dir): + py3_dir = os.path.normpath(path) + if os.path.isdir(py3_dir): break else: raise ApplicationLaunchFailed( - "Python 3.6 is not installed at the provided path.\n" - "Either make sure the environments in fusion settings has" - " 'PYTHON36' set corectly or make sure Python 3.6 is installed" - f" in the given path.\n\nPYTHON36: {fusion_python36_home}" + "Python 3 is not installed at the provided path.\n" + "Make sure the environment in fusion settings has " + "'FUSION_PYTHON3_HOME' set correctly and make sure " + "Python 3 is installed in the given path." + f"\n\nPYTHON36: {fusion_python3_home}" ) - self.log.info(f"Setting {py36_var}: '{py36_dir}'...") - self.launch_context.env[py36_var] = py36_dir + self.log.info(f"Setting {py3_var}: '{py3_dir}'...") + self.launch_context.env[py3_var] = py3_dir - # TODO: Set this for EITHER Fu16-17 OR Fu18+, don't do both - # Fusion 18+ does not look in FUSION16_PYTHON36_HOME anymore - # but instead uses FUSION_PYTHON3_HOME and requires the Python to - # be available on PATH to work. So let's enforce that for now. - self.launch_context.env["FUSION_PYTHON3_HOME"] = py36_dir - self.launch_context.env["PATH"] += ";" + py36_dir + # Fusion 18+ requires FUSION_PYTHON3_HOME to also be on PATH + self.launch_context.env["PATH"] += ";" + py3_dir + + # Fusion 16 and 17 use FUSION16_PYTHON36_HOME instead of + # FUSION_PYTHON3_HOME and will only work with a Python 3.6 version + # TODO: Detect Fusion version to only set for specific Fusion build + self.launch_context.env["FUSION16_PYTHON36_HOME"] = py3_dir # Add our Fusion Master Prefs which is the only way to customize # Fusion to define where it can read custom scripts and tools from self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR - pref_var = "FUSION16_MasterPrefs" # used by both Fu16 and Fu17 + pref_var = "FUSION16_MasterPrefs" # used by Fusion 16, 17 and 18 prefs = os.path.join(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") self.log.info(f"Setting {pref_var}: {prefs}") self.launch_context.env[pref_var] = prefs From c965b35549ebcde4b88380f1320376f52781a946 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 15:55:10 +0200 Subject: [PATCH 446/513] Fix typo --- openpype/hosts/fusion/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index dcf205ff6a..314acb7e78 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -70,8 +70,8 @@ def set_framerange(): asset_doc = get_current_project_asset() start = asset_doc["data"]["frameStart"] end = asset_doc["data"]["frameEnd"] - handle_start = asset_doc["data"]["handleStart"], - handle_end = asset_doc["data"]["handleEnd"], + handle_start = asset_doc["data"]["handleStart"] + handle_end = asset_doc["data"]["handleEnd"] update_frame_range(start, end, set_render_range=True, handle_start=handle_start, handle_end=handle_end) From 4154f76830f2bb96abccf8090ebf9b43c943ff6c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 18:03:06 +0200 Subject: [PATCH 447/513] Implement Set Asset Resolution --- openpype/hosts/fusion/api/lib.py | 15 +++++++++++++++ openpype/hosts/fusion/api/menu.py | 6 ++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 314acb7e78..b7e72e4c7a 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -77,6 +77,21 @@ def set_framerange(): handle_end=handle_end) +def set_resolution(): + """Set Comp's defaults""" + asset_doc = get_current_project_asset() + width = asset_doc["data"]["resolutionWidth"] + height = asset_doc["data"]["resolutionHeight"] + comp = get_current_comp() + + print("Setting comp frame format resolution to {}x{}".format(width, + height)) + comp.SetPrefs({ + "Comp.FrameFormat.Width": width, + "Comp.FrameFormat.Height": height, + }) + + def get_additional_data(container): """Get Fusion related data for the container diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index bba94053a2..84f680a918 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -9,8 +9,9 @@ from openpype.hosts.fusion.scripts import ( set_rendermode, duplicate_with_inputs ) -from openpype.hosts.fusion.api import ( - set_framerange +from openpype.hosts.fusion.api.lib import ( + set_framerange, + set_resolution ) from openpype.pipeline import legacy_io @@ -185,6 +186,7 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_set_resolution_clicked(self): print("Clicked Reset Resolution") + set_resolution() def on_set_framerange_clicked(self): print("Clicked Reset Framerange") From 830ccf0438947f6c2891611293e0f26b76874411 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 18:04:58 +0200 Subject: [PATCH 448/513] Clarify function names --- openpype/hosts/fusion/api/__init__.py | 4 ++-- openpype/hosts/fusion/api/lib.py | 4 ++-- openpype/hosts/fusion/api/menu.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/fusion/api/__init__.py b/openpype/hosts/fusion/api/__init__.py index 78afabdb45..e5a7dac8c8 100644 --- a/openpype/hosts/fusion/api/__init__.py +++ b/openpype/hosts/fusion/api/__init__.py @@ -24,7 +24,7 @@ from .lib import ( maintained_selection, get_additional_data, update_frame_range, - set_framerange + set_asset_framerange ) from .menu import launch_openpype_menu @@ -54,7 +54,7 @@ __all__ = [ "maintained_selection", "get_additional_data", "update_frame_range", - "set_framerange", + "set_asset_framerange", # menu "launch_openpype_menu", diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index b7e72e4c7a..85afc11e1c 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -66,7 +66,7 @@ def update_frame_range(start, end, comp=None, set_render_range=True, comp.SetAttrs(attrs) -def set_framerange(): +def set_asset_framerange(): asset_doc = get_current_project_asset() start = asset_doc["data"]["frameStart"] end = asset_doc["data"]["frameEnd"] @@ -77,7 +77,7 @@ def set_framerange(): handle_end=handle_end) -def set_resolution(): +def set_asset_resolution(): """Set Comp's defaults""" asset_doc = get_current_project_asset() width = asset_doc["data"]["resolutionWidth"] diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 84f680a918..4819fd6c7c 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -10,8 +10,8 @@ from openpype.hosts.fusion.scripts import ( duplicate_with_inputs ) from openpype.hosts.fusion.api.lib import ( - set_framerange, - set_resolution + set_asset_framerange, + set_asset_resolution ) from openpype.pipeline import legacy_io @@ -186,11 +186,11 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_set_resolution_clicked(self): print("Clicked Reset Resolution") - set_resolution() + set_asset_resolution() def on_set_framerange_clicked(self): print("Clicked Reset Framerange") - set_framerange() + set_asset_framerange() def launch_openpype_menu(): From 863265fb41cbb7aec7111833658fd21c4f38af23 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 18:06:22 +0200 Subject: [PATCH 449/513] Set `OPENPYPE_LOG_NO_COLORS` in addon --- openpype/hosts/fusion/addon.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/hosts/fusion/addon.py b/openpype/hosts/fusion/addon.py index e257005061..1913cc2e30 100644 --- a/openpype/hosts/fusion/addon.py +++ b/openpype/hosts/fusion/addon.py @@ -19,5 +19,14 @@ class FusionAddon(OpenPypeModule, IHostAddon): os.path.join(FUSION_HOST_DIR, "hooks") ] + def add_implementation_envs(self, env, _app): + # Set default values if are not already set via settings + defaults = { + "OPENPYPE_LOG_NO_COLORS": "Yes" + } + for key, value in defaults.items(): + if not env.get(key): + env[key] = value + def get_workfile_extensions(self): return [".comp"] From df330e1002420a51b87117526f5c7a910f152279 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 18:07:24 +0200 Subject: [PATCH 450/513] Update defaults for Fusion environment --- .../system_settings/applications.json | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 4a3e0c1b94..c37c3d299e 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -706,28 +706,11 @@ "icon": "{}/app_icons/fusion.png", "host_name": "fusion", "environment": { - "FUSION_UTILITY_SCRIPTS_SOURCE_DIR": [], - "FUSION_UTILITY_SCRIPTS_DIR": { - "windows": "{PROGRAMDATA}/Blackmagic Design/Fusion/Scripts/Comp", - "darwin": "/Library/Application Support/Blackmagic Design/Fusion/Scripts/Comp", - "linux": "/opt/Fusion/Scripts/Comp" - }, - "PYTHON36": { + "FUSION_PYTHON3_HOME": { "windows": "{LOCALAPPDATA}/Programs/Python/Python36", "darwin": "~/Library/Python/3.6/bin", "linux": "/opt/Python/3.6/bin" - }, - "PYTHONPATH": [ - "{PYTHON36}/Lib/site-packages", - "{VIRTUAL_ENV}/Lib/site-packages", - "{PYTHONPATH}" - ], - "PATH": [ - "{PYTHON36}", - "{PYTHON36}/Scripts", - "{PATH}" - ], - "OPENPYPE_LOG_NO_COLORS": "Yes" + } }, "variants": { "18": { From c725c6affbcf474a7b9f9430e3d26933898ebb1d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 19 Sep 2022 18:12:41 +0200 Subject: [PATCH 451/513] Tweak docstrings --- openpype/hosts/fusion/api/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 85afc11e1c..91a74ac848 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -67,6 +67,7 @@ def update_frame_range(start, end, comp=None, set_render_range=True, def set_asset_framerange(): + """Set Comp's frame range based on current asset""" asset_doc = get_current_project_asset() start = asset_doc["data"]["frameStart"] end = asset_doc["data"]["frameEnd"] @@ -78,7 +79,7 @@ def set_asset_framerange(): def set_asset_resolution(): - """Set Comp's defaults""" + """Set Comp's resolution width x height default based on current asset""" asset_doc = get_current_project_asset() width = asset_doc["data"]["resolutionWidth"] height = asset_doc["data"]["resolutionHeight"] From 56dcb25dc056b892af0939236d18abda6967ee96 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 18:23:08 +0200 Subject: [PATCH 452/513] Updated args to maketx Better logging. --- .../maya/plugins/publish/extract_look.py | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index 91b0da75c6..dd705324b9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -13,7 +13,7 @@ from maya import cmds # noqa import pyblish.api -from openpype.lib import source_hash +from openpype.lib import source_hash, run_subprocess from openpype.pipeline import legacy_io, publish from openpype.hosts.maya.api import lib @@ -68,7 +68,7 @@ def find_paths_by_hash(texture_hash): return legacy_io.distinct(key, {"type": "version"}) -def maketx(source, destination, *args): +def maketx(source, destination, args, logger): """Make `.tx` using `maketx` with some default settings. The settings are based on default as used in Arnold's @@ -79,7 +79,8 @@ def maketx(source, destination, *args): Args: source (str): Path to source file. destination (str): Writing destination path. - *args: Additional arguments for `maketx`. + args: Additional arguments for `maketx`. + logger Returns: str: Output of `maketx` command. @@ -94,7 +95,7 @@ def maketx(source, destination, *args): "OIIO tool not found in {}".format(maketx_path)) raise AssertionError("OIIO tool not found") - cmd = [ + subprocess_args = [ maketx_path, "-v", # verbose "-u", # update mode @@ -103,27 +104,20 @@ def maketx(source, destination, *args): "--checknan", # use oiio-optimized settings for tile-size, planarconfig, metadata "--oiio", - "--filter lanczos3", - escape_space(source) + "--filter", "lanczos3", + source ] - cmd.extend(args) - cmd.extend(["-o", escape_space(destination)]) + subprocess_args.extend(args) + subprocess_args.extend(["-o", destination]) - cmd = " ".join(cmd) + cmd = " ".join(subprocess_args) + logger.debug(cmd) - CREATE_NO_WINDOW = 0x08000000 # noqa - kwargs = dict(args=cmd, stderr=subprocess.STDOUT) - - if sys.platform == "win32": - kwargs["creationflags"] = CREATE_NO_WINDOW try: - out = subprocess.check_output(**kwargs) - except subprocess.CalledProcessError as exc: - print(exc) - import traceback - - traceback.print_exc() + out = run_subprocess(subprocess_args) + except Exception: + logger.error("Maketx converion failed", exc_info=True) raise return out @@ -524,15 +518,17 @@ class ExtractLook(publish.Extractor): if do_maketx and ext != ".tx": # Produce .tx file in staging if source file is not .tx converted = os.path.join(staging, "resources", fname + ".tx") - + additional_args = [ + "--sattrib", + "sourceHash", + texture_hash + ] if linearize: self.log.info("tx: converting sRGB -> linear") - colorconvert = "--colorconvert sRGB linear" - else: - colorconvert = "" + additional_args.extend(["--colorconvert", "sRGB", "linear"]) config_path = get_ocio_config_path("nuke-default") - color_config = "--colorconfig {0}".format(config_path) + additional_args.extend(["--colorconfig", config_path]) # Ensure folder exists if not os.path.exists(os.path.dirname(converted)): os.makedirs(os.path.dirname(converted)) @@ -541,12 +537,8 @@ class ExtractLook(publish.Extractor): maketx( filepath, converted, - # Include `source-hash` as string metadata - "--sattrib", - "sourceHash", - escape_space(texture_hash), - colorconvert, - color_config + additional_args, + self.log ) return converted, COPY, texture_hash From 1f035a1eee956d56b55e86dd97f5090fdab81894 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 01:24:24 +0200 Subject: [PATCH 453/513] Store link to menu so we can get access to it elsewhere --- openpype/hosts/fusion/api/menu.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 4819fd6c7c..cf3dea8ec3 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -17,6 +17,9 @@ from openpype.pipeline import legacy_io from .pulse import FusionPulse +self = sys.modules[__name__] +self.menu = None + class Spacer(QtWidgets.QWidget): def __init__(self, height, *args, **kwargs): @@ -202,6 +205,7 @@ def launch_openpype_menu(): pype_menu.setStyleSheet(stylesheet) pype_menu.show() + self.menu = pype_menu result = app.exec_() print("Shutting down..") From 23b6a35266ef14f8bccbe8edfc1551ce63a0f0b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 01:26:20 +0200 Subject: [PATCH 454/513] Add after open callback to show popup about outdated containers --- openpype/hosts/fusion/api/pipeline.py | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 7f9a57dc0f..b5d461e7f0 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -8,7 +8,10 @@ import contextlib import pyblish.api -from openpype.lib import Logger +from openpype.lib import ( + Logger, + register_event_callback +) from openpype.pipeline import ( register_loader_plugin_path, register_creator_plugin_path, @@ -18,6 +21,7 @@ from openpype.pipeline import ( deregister_inventory_action_path, AVALON_CONTAINER_ID, ) +from openpype.pipeline.load import any_outdated_containers from openpype.hosts.fusion import FUSION_HOST_DIR log = Logger.get_logger(__name__) @@ -77,6 +81,11 @@ def install(): "instanceToggled", on_pyblish_instance_toggled ) + # Fusion integration currently does not attach to direct callbacks of + # the application. So we use workfile callbacks to allow similar behavior + # on save and open + register_event_callback("workfile.open.after", on_after_open) + def uninstall(): """Uninstall all that was installed @@ -125,6 +134,34 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): tool.SetAttrs({"TOOLB_PassThrough": passthrough}) +def on_after_open(_event): + + if any_outdated_containers(): + log.warning("Scene has outdated content.") + + # Find OpenPype menu to attach to + from . import menu + + comp = get_current_comp() + + def _on_show_scene_inventory(): + comp.CurrentFrame.ActivateFrame() # ensure that comp is active + host_tools.show_scene_inventory() + + from openpype.widgets import popup + from openpype.style import load_stylesheet + dialog = popup.Popup(parent=menu.menu) + dialog.setWindowTitle("Fusion comp has outdated content") + dialog.setMessage("There are outdated containers in " + "your Fusion comp.") + dialog.on_clicked.connect(_on_show_scene_inventory) + + dialog.show() + dialog.raise_() + dialog.activateWindow() + dialog.setStyleSheet(load_stylesheet()) + + def ls(): """List containers from active Fusion scene From 30ee358fc1e7b127981fcf4c1e897e7dc65a0de7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 01:29:01 +0200 Subject: [PATCH 455/513] Move `get_current_comp` and `comp_lock_and_undo_chunk` to `lib` + fix import of host tools --- openpype/hosts/fusion/api/__init__.py | 14 ++++++-------- openpype/hosts/fusion/api/lib.py | 20 ++++++++++++++++++-- openpype/hosts/fusion/api/pipeline.py | 22 ++++++---------------- openpype/hosts/fusion/api/workio.py | 2 +- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/fusion/api/__init__.py b/openpype/hosts/fusion/api/__init__.py index e5a7dac8c8..45ed4e12a3 100644 --- a/openpype/hosts/fusion/api/__init__.py +++ b/openpype/hosts/fusion/api/__init__.py @@ -5,10 +5,7 @@ from .pipeline import ( ls, imprint_container, - parse_container, - - get_current_comp, - comp_lock_and_undo_chunk + parse_container ) from .workio import ( @@ -24,7 +21,9 @@ from .lib import ( maintained_selection, get_additional_data, update_frame_range, - set_asset_framerange + set_asset_framerange, + get_current_comp, + comp_lock_and_undo_chunk ) from .menu import launch_openpype_menu @@ -39,9 +38,6 @@ __all__ = [ "imprint_container", "parse_container", - "get_current_comp", - "comp_lock_and_undo_chunk", - # workio "open_file", "save_file", @@ -55,6 +51,8 @@ __all__ = [ "get_additional_data", "update_frame_range", "set_asset_framerange", + "get_current_comp", + "comp_lock_and_undo_chunk", # menu "launch_openpype_menu", diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 91a74ac848..295ba33711 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -19,8 +19,6 @@ from openpype.pipeline import ( ) from openpype.pipeline.context_tools import get_current_project_asset -from .pipeline import get_current_comp, comp_lock_and_undo_chunk - self = sys.modules[__name__] self._project = None @@ -232,3 +230,21 @@ def get_frame_path(path): padding = 4 # default Fusion padding return filename, padding, ext + + +def get_current_comp(): + """Hack to get current comp in this session""" + fusion = getattr(sys.modules["__main__"], "fusion", None) + return fusion.CurrentComp if fusion else None + + +@contextlib.contextmanager +def comp_lock_and_undo_chunk(comp, undo_queue_name="Script CMD"): + """Lock comp and open an undo chunk during the context""" + try: + comp.Lock() + comp.StartUndo(undo_queue_name) + yield + finally: + comp.Unlock() + comp.EndUndo() \ No newline at end of file diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index b5d461e7f0..82cae427ff 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -23,6 +23,12 @@ from openpype.pipeline import ( ) from openpype.pipeline.load import any_outdated_containers from openpype.hosts.fusion import FUSION_HOST_DIR +from openpype.tools.utils import host_tools + +from .lib import ( + get_current_comp, + comp_lock_and_undo_chunk +) log = Logger.get_logger(__name__) @@ -247,19 +253,3 @@ def parse_container(tool): return container -def get_current_comp(): - """Hack to get current comp in this session""" - fusion = getattr(sys.modules["__main__"], "fusion", None) - return fusion.CurrentComp if fusion else None - - -@contextlib.contextmanager -def comp_lock_and_undo_chunk(comp, undo_queue_name="Script CMD"): - """Lock comp and open an undo chunk during the context""" - try: - comp.Lock() - comp.StartUndo(undo_queue_name) - yield - finally: - comp.Unlock() - comp.EndUndo() diff --git a/openpype/hosts/fusion/api/workio.py b/openpype/hosts/fusion/api/workio.py index 89752d3e6d..939b2ff4be 100644 --- a/openpype/hosts/fusion/api/workio.py +++ b/openpype/hosts/fusion/api/workio.py @@ -2,7 +2,7 @@ import sys import os -from .pipeline import get_current_comp +from .lib import get_current_comp def file_extensions(): From 9dda39a3e722e8a888f94de06072830cf241252d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 01:29:24 +0200 Subject: [PATCH 456/513] Remove unused imports --- openpype/hosts/fusion/api/pipeline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 82cae427ff..6d4a20ccee 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -2,9 +2,7 @@ Basic avalon integration """ import os -import sys import logging -import contextlib import pyblish.api From 15c3b068285fe2021d35822c32ad9d5b85e8574e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 01:36:31 +0200 Subject: [PATCH 457/513] Add validate comp prefs on scene before save and after scene open --- openpype/hosts/fusion/api/lib.py | 44 +++++++++++++++++++++++++++ openpype/hosts/fusion/api/pipeline.py | 9 +++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 295ba33711..f10809a7e1 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -5,6 +5,7 @@ import contextlib from Qt import QtGui +from openpype.lib import Logger from openpype.client import ( get_asset_by_name, get_subset_by_name, @@ -91,6 +92,49 @@ def set_asset_resolution(): }) +def validate_comp_prefs(): + """Validate current comp defaults with asset settings. + + Validates fps, resolutionWidth, resolutionHeight, aspectRatio. + + This does *not* validate frameStart, frameEnd, handleStart and handleEnd. + """ + + log = Logger.get_logger("validate_comp_prefs") + + fields = [ + "data.fps", + "data.resolutionWidth", + "data.resolutionHeight", + "data.pixelAspect" + ] + asset_data = get_current_project_asset(fields=fields)["data"] + + comp = get_current_comp() + comp_frame_format_prefs = comp.GetPrefs("Comp.FrameFormat") + + # Pixel aspect ratio in Fusion is set as AspectX and AspectY so we convert + # the data to something that is more sensible to Fusion + asset_data["pixelAspectX"] = asset_data.pop("pixelAspect") + asset_data["pixelAspectY"] = 1.0 + + for key, comp_key, label in [ + ("fps", "Rate", "FPS"), + ("resolutionWidth", "Width", "Resolution Width"), + ("resolutionHeight", "Height", "Resolution Height"), + ("pixelAspectX", "AspectX", "Pixel Aspect Ratio X"), + ("pixelAspectY", "AspectY", "Pixel Aspect Ratio Y") + ]: + value = asset_data[key] + current_value = comp_frame_format_prefs.get(comp_key) + if value != current_value: + # todo: Actually show dialog to user instead of just logging + log.warning( + "Invalid pref {}: {} (should be: {})".format(comp_key, + current_value, + value)) + + def get_additional_data(container): """Get Fusion related data for the container diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 6d4a20ccee..c2c39291c6 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -25,7 +25,8 @@ from openpype.tools.utils import host_tools from .lib import ( get_current_comp, - comp_lock_and_undo_chunk + comp_lock_and_undo_chunk, + validate_comp_prefs ) log = Logger.get_logger(__name__) @@ -88,6 +89,7 @@ def install(): # Fusion integration currently does not attach to direct callbacks of # the application. So we use workfile callbacks to allow similar behavior # on save and open + register_event_callback("workfile.save.before", on_before_save) register_event_callback("workfile.open.after", on_after_open) @@ -138,7 +140,12 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): tool.SetAttrs({"TOOLB_PassThrough": passthrough}) +def on_before_save(_event): + validate_comp_prefs() + + def on_after_open(_event): + validate_comp_prefs() if any_outdated_containers(): log.warning("Scene has outdated content.") From fb940c5da099dd56cba7cbfedf9143e29e659b4a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 01:39:42 +0200 Subject: [PATCH 458/513] Don't error if "show" is clicked but comp is closed already --- openpype/hosts/fusion/api/pipeline.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index c2c39291c6..bc7f90a97c 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -156,7 +156,12 @@ def on_after_open(_event): comp = get_current_comp() def _on_show_scene_inventory(): - comp.CurrentFrame.ActivateFrame() # ensure that comp is active + # ensure that comp is active + frame = comp.CurrentFrame + if not frame: + print("Comp is closed, skipping show scene inventory") + return + frame.ActivateFrame() # raise comp window host_tools.show_scene_inventory() from openpype.widgets import popup From 1013a293519aed5470f1eda4e2ba4f280d718dba Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 01:41:04 +0200 Subject: [PATCH 459/513] Remove double space --- openpype/hosts/fusion/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index bc7f90a97c..083aa50027 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -167,7 +167,7 @@ def on_after_open(_event): from openpype.widgets import popup from openpype.style import load_stylesheet dialog = popup.Popup(parent=menu.menu) - dialog.setWindowTitle("Fusion comp has outdated content") + dialog.setWindowTitle("Fusion comp has outdated content") dialog.setMessage("There are outdated containers in " "your Fusion comp.") dialog.on_clicked.connect(_on_show_scene_inventory) From 2df5d871f16338a425870649a0df59d189acdaf4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 02:07:30 +0200 Subject: [PATCH 460/513] Remove before save callback since it runs BEFORE the task change making it unusable for these particular validations --- openpype/hosts/fusion/api/pipeline.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 083aa50027..76b365e29f 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -89,7 +89,6 @@ def install(): # Fusion integration currently does not attach to direct callbacks of # the application. So we use workfile callbacks to allow similar behavior # on save and open - register_event_callback("workfile.save.before", on_before_save) register_event_callback("workfile.open.after", on_after_open) @@ -140,10 +139,6 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): tool.SetAttrs({"TOOLB_PassThrough": passthrough}) -def on_before_save(_event): - validate_comp_prefs() - - def on_after_open(_event): validate_comp_prefs() @@ -171,7 +166,6 @@ def on_after_open(_event): dialog.setMessage("There are outdated containers in " "your Fusion comp.") dialog.on_clicked.connect(_on_show_scene_inventory) - dialog.show() dialog.raise_() dialog.activateWindow() From 4c884ed2bc0649a5a6a08772fedd48ce06c1bda9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 02:08:09 +0200 Subject: [PATCH 461/513] Add a pop-up about invalid comp configuration --- openpype/hosts/fusion/api/lib.py | 58 +++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index f10809a7e1..a95312b938 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -103,12 +103,14 @@ def validate_comp_prefs(): log = Logger.get_logger("validate_comp_prefs") fields = [ + "name", "data.fps", "data.resolutionWidth", "data.resolutionHeight", "data.pixelAspect" ] - asset_data = get_current_project_asset(fields=fields)["data"] + asset_doc = get_current_project_asset(fields=fields) + asset_data = asset_doc["data"] comp = get_current_comp() comp_frame_format_prefs = comp.GetPrefs("Comp.FrameFormat") @@ -118,21 +120,59 @@ def validate_comp_prefs(): asset_data["pixelAspectX"] = asset_data.pop("pixelAspect") asset_data["pixelAspectY"] = 1.0 - for key, comp_key, label in [ + validations = [ ("fps", "Rate", "FPS"), ("resolutionWidth", "Width", "Resolution Width"), ("resolutionHeight", "Height", "Resolution Height"), ("pixelAspectX", "AspectX", "Pixel Aspect Ratio X"), ("pixelAspectY", "AspectY", "Pixel Aspect Ratio Y") - ]: - value = asset_data[key] - current_value = comp_frame_format_prefs.get(comp_key) - if value != current_value: + ] + + invalid = [] + for key, comp_key, label in validations: + asset_value = asset_data[key] + comp_value = comp_frame_format_prefs.get(comp_key) + if asset_value != comp_value: # todo: Actually show dialog to user instead of just logging log.warning( - "Invalid pref {}: {} (should be: {})".format(comp_key, - current_value, - value)) + "Comp {pref} {value} does not match asset " + "'{asset_name}' {pref} {asset_value}".format( + pref=label, + value=comp_value, + asset_name=asset_doc["name"], + asset_value=asset_value) + ) + + invalid_msg = "{} {} should be {}".format(label, + comp_value, + asset_value) + invalid.append(invalid_msg) + + if invalid: + + def _on_repair(): + attributes = dict() + for key, comp_key, _label in validations: + value = asset_data[key] + comp_key_full = "Comp.FrameFormat.{}".format(comp_key) + attributes[comp_key_full] = value + comp.SetPrefs(attributes) + + from . import menu + from openpype.widgets import popup + from openpype.style import load_stylesheet + dialog = popup.Popup(parent=menu.menu) + dialog.setWindowTitle("Fusion comp has invalid configuration") + + msg = "Comp preferences mismatches '{}'".format(asset_doc["name"]) + msg += "\n" + "\n".join(invalid) + dialog.setMessage(msg) + dialog.setButtonText("Repair") + dialog.on_clicked.connect(_on_repair) + dialog.show() + dialog.raise_() + dialog.activateWindow() + dialog.setStyleSheet(load_stylesheet()) def get_additional_data(container): From 794e8a9b8504ccf99d0795d54b7ab9ec3feb6a78 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 08:35:23 +0200 Subject: [PATCH 462/513] Shush hound --- openpype/hosts/fusion/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index a95312b938..a7472d239c 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -331,4 +331,4 @@ def comp_lock_and_undo_chunk(comp, undo_queue_name="Script CMD"): yield finally: comp.Unlock() - comp.EndUndo() \ No newline at end of file + comp.EndUndo() From c43b952357c9c69798adcd3fb04fc2d6dbc85100 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 20 Sep 2022 11:15:39 +0200 Subject: [PATCH 463/513] Updated docstring Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/extract_look.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index dd705324b9..403b4ee6bc 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -79,8 +79,8 @@ def maketx(source, destination, args, logger): Args: source (str): Path to source file. destination (str): Writing destination path. - args: Additional arguments for `maketx`. - logger + args (list): Additional arguments for `maketx`. + logger (logging.Logger): Logger to log messages to. Returns: str: Output of `maketx` command. From cefe853cc6c932771ffd9f2325c783942fbff3da Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 20 Sep 2022 11:29:23 +0200 Subject: [PATCH 464/513] OP-3943 - changed label to Active Active label matches configuration for optional plugins. Collector cannot be optional, but Active matches to "Instance is collected but won't get published by default." --- .../schemas/projects_schema/schema_project_photoshop.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 7294ba8608..26b38fa2c6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -140,7 +140,7 @@ { "type": "boolean", "key": "publish", - "label": "Publish review" + "label": "Active" } ] }, From 7e65bdd096b7ee3c9ae927374d2c5c89270cd9b3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 20 Sep 2022 16:39:00 +0200 Subject: [PATCH 465/513] OP-3923 - fix issue in Hero hardlinks Disk must be NTFS format or it will throw "WinError 1", which matches to EINVAL. Still raise different errors. --- openpype/plugins/publish/integrate_hero_version.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 96d768e1c1..c0760a5471 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -577,8 +577,11 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): return except OSError as exc: - # re-raise exception if different than cross drive path - if exc.errno != errno.EXDEV: + # re-raise exception if different than + # EXDEV - cross drive path + # EINVAL - wrong format, must be NTFS + self.log.debug("Hardlink failed with errno:'{}'".format(exc.errno)) + if exc.errno not in [errno.EXDEV, errno.EINVAL]: raise shutil.copy(src_path, dst_path) From b0f7db52c84e40fa7fadfeb2fd180e2874fe950c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 20 Sep 2022 17:09:58 +0200 Subject: [PATCH 466/513] make sure the output is always the same --- openpype/hosts/tvpaint/lib.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/tvpaint/lib.py b/openpype/hosts/tvpaint/lib.py index bf47e725cb..95653b6ecb 100644 --- a/openpype/hosts/tvpaint/lib.py +++ b/openpype/hosts/tvpaint/lib.py @@ -646,9 +646,6 @@ def rename_filepaths_by_frame_start( filepaths_by_frame, range_start, range_end, new_frame_start ): """Change frames in filenames of finished images to new frame start.""" - # Skip if source first frame is same as destination first frame - if range_start == new_frame_start: - return {} # Calculate frame end new_frame_end = range_end + (new_frame_start - range_start) @@ -669,14 +666,17 @@ def rename_filepaths_by_frame_start( source_range = range(range_start, range_end + 1) output_range = range(new_frame_start, new_frame_end + 1) + # Skip if source first frame is same as destination first frame new_dst_filepaths = {} for src_frame, dst_frame in zip(source_range, output_range): - src_filepath = filepaths_by_frame[src_frame] - src_dirpath = os.path.dirname(src_filepath) + src_filepath = os.path.normpath(filepaths_by_frame[src_frame]) + dirpath, src_filename = os.path.split(src_filepath) dst_filename = filename_template.format(frame=dst_frame) - dst_filepath = os.path.join(src_dirpath, dst_filename) + dst_filepath = os.path.join(dirpath, dst_filename) - os.rename(src_filepath, dst_filepath) + if src_filename != dst_filename: + os.rename(src_filepath, dst_filepath) new_dst_filepaths[dst_frame] = dst_filepath + return new_dst_filepaths From 64ef00b564eaccc0cad74e4c59e318724f4a8315 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 16:01:26 +0200 Subject: [PATCH 467/513] Set OpenPype icon for menu (cherry picked from commit c902d7b8130ae21b1808e54643b1de59a54f5c43) --- openpype/hosts/fusion/api/menu.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index cf3dea8ec3..ba0e267b14 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -1,6 +1,6 @@ import sys -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui from openpype.tools.utils import host_tools from openpype.style import load_stylesheet @@ -14,6 +14,7 @@ from openpype.hosts.fusion.api.lib import ( set_asset_resolution ) from openpype.pipeline import legacy_io +from openpype.resources import get_openpype_icon_filepath from .pulse import FusionPulse @@ -44,6 +45,10 @@ class OpenPypeMenu(QtWidgets.QWidget): self.setObjectName("OpenPypeMenu") + icon_path = get_openpype_icon_filepath() + icon = QtGui.QIcon(icon_path) + self.setWindowIcon(icon) + self.setWindowFlags( QtCore.Qt.Window | QtCore.Qt.CustomizeWindowHint From a8d90473b8e9bf331a3ff740095bc843c17afb2a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 19:42:21 +0200 Subject: [PATCH 468/513] Remove redundant Spacer widget and use `QVBoxLayout.addSpacing` --- openpype/hosts/fusion/api/menu.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index ba0e267b14..949b905705 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -22,23 +22,6 @@ self = sys.modules[__name__] self.menu = None -class Spacer(QtWidgets.QWidget): - def __init__(self, height, *args, **kwargs): - super(Spacer, self).__init__(*args, **kwargs) - - self.setFixedHeight(height) - - real_spacer = QtWidgets.QWidget(self) - real_spacer.setObjectName("Spacer") - real_spacer.setFixedHeight(height) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(real_spacer) - - self.setLayout(layout) - - class OpenPypeMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): super(OpenPypeMenu, self).__init__(*args, **kwargs) @@ -86,28 +69,28 @@ class OpenPypeMenu(QtWidgets.QWidget): layout.addWidget(asset_label) - layout.addWidget(Spacer(15, self)) + layout.addSpacing(20) layout.addWidget(workfiles_btn) - layout.addWidget(Spacer(15, self)) + layout.addSpacing(20) layout.addWidget(create_btn) layout.addWidget(load_btn) layout.addWidget(publish_btn) layout.addWidget(manager_btn) - layout.addWidget(Spacer(15, self)) + layout.addSpacing(20) layout.addWidget(libload_btn) - layout.addWidget(Spacer(15, self)) + layout.addSpacing(20) layout.addWidget(set_framerange_btn) layout.addWidget(set_resolution_btn) layout.addWidget(rendermode_btn) - layout.addWidget(Spacer(15, self)) + layout.addSpacing(20) layout.addWidget(duplicate_with_inputs_btn) From 1ce7e697ec2b64f135113e8e3c8832c2ada33972 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 19:43:56 +0200 Subject: [PATCH 469/513] Remove "Clicked {button}" print statements - UIs have pretty much no delays so no need to print --- openpype/hosts/fusion/api/menu.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 949b905705..7a6293807f 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -138,31 +138,24 @@ class OpenPypeMenu(QtWidgets.QWidget): self._callbacks[:] = [] def on_workfile_clicked(self): - print("Clicked Workfile") host_tools.show_workfiles() def on_create_clicked(self): - print("Clicked Create") host_tools.show_creator() def on_publish_clicked(self): - print("Clicked Publish") host_tools.show_publish() def on_load_clicked(self): - print("Clicked Load") host_tools.show_loader(use_context=True) def on_manager_clicked(self): - print("Clicked Manager") host_tools.show_scene_inventory() def on_libload_clicked(self): - print("Clicked Library") host_tools.show_library_loader() def on_rendermode_clicked(self): - print("Clicked Set Render Mode") if self.render_mode_widget is None: window = set_rendermode.SetRenderMode() window.setStyleSheet(load_stylesheet()) @@ -172,15 +165,12 @@ class OpenPypeMenu(QtWidgets.QWidget): self.render_mode_widget.show() def on_duplicate_with_inputs_clicked(self): - print("Clicked Duplicate with input connections") duplicate_with_inputs.duplicate_with_input_connections() def on_set_resolution_clicked(self): - print("Clicked Reset Resolution") set_asset_resolution() def on_set_framerange_clicked(self): - print("Clicked Reset Framerange") set_asset_framerange() From 15610328be6f94ed00aaaa166062438bc8bda3f6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 19:46:13 +0200 Subject: [PATCH 470/513] Get current comp once and as early as possible --- openpype/hosts/fusion/api/lib.py | 6 ++++-- openpype/hosts/fusion/api/pipeline.py | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index a7472d239c..956f3557ad 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -92,7 +92,7 @@ def set_asset_resolution(): }) -def validate_comp_prefs(): +def validate_comp_prefs(comp=None): """Validate current comp defaults with asset settings. Validates fps, resolutionWidth, resolutionHeight, aspectRatio. @@ -100,6 +100,9 @@ def validate_comp_prefs(): This does *not* validate frameStart, frameEnd, handleStart and handleEnd. """ + if comp is None: + comp = get_current_comp() + log = Logger.get_logger("validate_comp_prefs") fields = [ @@ -112,7 +115,6 @@ def validate_comp_prefs(): asset_doc = get_current_project_asset(fields=fields) asset_data = asset_doc["data"] - comp = get_current_comp() comp_frame_format_prefs = comp.GetPrefs("Comp.FrameFormat") # Pixel aspect ratio in Fusion is set as AspectX and AspectY so we convert diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 76b365e29f..7933f160dc 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -140,7 +140,8 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): def on_after_open(_event): - validate_comp_prefs() + comp = get_current_comp() + validate_comp_prefs(comp) if any_outdated_containers(): log.warning("Scene has outdated content.") @@ -148,8 +149,6 @@ def on_after_open(_event): # Find OpenPype menu to attach to from . import menu - comp = get_current_comp() - def _on_show_scene_inventory(): # ensure that comp is active frame = comp.CurrentFrame From 6bcb9dd2471cf39dc21f2ea71e05cdfe4fea650c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 20:01:53 +0200 Subject: [PATCH 471/513] Remove `get_additional_data` OpenPype manager/containers don't use it so it's redundant in the code base. --- openpype/hosts/fusion/api/__init__.py | 2 -- openpype/hosts/fusion/api/lib.py | 20 -------------------- 2 files changed, 22 deletions(-) diff --git a/openpype/hosts/fusion/api/__init__.py b/openpype/hosts/fusion/api/__init__.py index 45ed4e12a3..ed70dbca50 100644 --- a/openpype/hosts/fusion/api/__init__.py +++ b/openpype/hosts/fusion/api/__init__.py @@ -19,7 +19,6 @@ from .workio import ( from .lib import ( maintained_selection, - get_additional_data, update_frame_range, set_asset_framerange, get_current_comp, @@ -48,7 +47,6 @@ __all__ = [ # lib "maintained_selection", - "get_additional_data", "update_frame_range", "set_asset_framerange", "get_current_comp", diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 956f3557ad..4ef44dbb61 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -177,26 +177,6 @@ def validate_comp_prefs(comp=None): dialog.setStyleSheet(load_stylesheet()) -def get_additional_data(container): - """Get Fusion related data for the container - - Args: - container(dict): the container found by the ls() function - - Returns: - dict - """ - - tool = container["_tool"] - tile_color = tool.TileColor - if tile_color is None: - return {} - - return {"color": QtGui.QColor.fromRgbF(tile_color["R"], - tile_color["G"], - tile_color["B"])} - - def switch_item(container, asset_name=None, subset_name=None, From 3c134ec011ae2b8ffecda43f1281b77a59d591bc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 20:05:15 +0200 Subject: [PATCH 472/513] Remove redundant saying "installed" even though it's midway during install + fix comment --- openpype/hosts/fusion/api/pipeline.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 7933f160dc..260c7d9e60 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -60,7 +60,7 @@ def install(): """ # Remove all handlers associated with the root logger object, because - # that one sometimes logs as "warnings" incorrectly. + # that one always logs as "warnings" incorrectly. for handler in logging.root.handlers[:]: logging.root.removeHandler(handler) @@ -72,8 +72,6 @@ def install(): logger.addHandler(handler) logger.setLevel(logging.DEBUG) - log.info("openpype.hosts.fusion installed") - pyblish.api.register_host("fusion") pyblish.api.register_plugin_path(PUBLISH_PATH) log.info("Registering Fusion plug-ins..") From 0b525fa3658c926be033afda4462b4769b1aa025 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 20:17:33 +0200 Subject: [PATCH 473/513] Cleanup Create EXR saver logic --- .../fusion/plugins/create/create_exr_saver.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/fusion/plugins/create/create_exr_saver.py b/openpype/hosts/fusion/plugins/create/create_exr_saver.py index 8bab5ee9b1..6d93fe710a 100644 --- a/openpype/hosts/fusion/plugins/create/create_exr_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_exr_saver.py @@ -1,6 +1,9 @@ import os -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + legacy_io +) from openpype.hosts.fusion.api import ( get_current_comp, comp_lock_and_undo_chunk @@ -21,12 +24,9 @@ class CreateOpenEXRSaver(LegacyCreator): comp = get_current_comp() - # todo: improve method of getting current environment - # todo: pref avalon.Session over os.environ + workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) - workdir = os.path.normpath(os.environ["AVALON_WORKDIR"]) - - filename = "{}..tiff".format(self.name) + filename = "{}..exr".format(self.name) filepath = os.path.join(workdir, "render", filename) with comp_lock_and_undo_chunk(comp): @@ -39,10 +39,10 @@ class CreateOpenEXRSaver(LegacyCreator): saver["Clip"] = filepath saver["OutputFormat"] = file_format - # # # Set standard TIFF settings + # Check file format settings are available if saver[file_format] is None: - raise RuntimeError("File format is not set to TiffFormat, " - "this is a bug") + raise RuntimeError("File format is not set to {}, " + "this is a bug".format(file_format)) # Set file format attributes saver[file_format]["Depth"] = 1 # int8 | int16 | float32 | other From b62d12779a67a266a3109af0433eb234397200a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 20:17:52 +0200 Subject: [PATCH 474/513] Clean up docstring --- openpype/hosts/fusion/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 260c7d9e60..c92d072ef7 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -48,7 +48,7 @@ class CompLogHandler(logging.Handler): def install(): - """Install fusion-specific functionality of avalon-core. + """Install fusion-specific functionality of OpenPype. This is where you install menus and register families, data and loaders into fusion. From c693af79cf374efb04b9d113f747e02baaf95ed1 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 21 Sep 2022 04:19:54 +0000 Subject: [PATCH 475/513] [Automated] Bump version --- CHANGELOG.md | 26 +++++++++++--------------- openpype/version.py | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af347cadfe..f868e6ed6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,18 @@ # Changelog -## [3.14.3-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.3-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.2...HEAD) **🚀 Enhancements** +- Maya: better logging in Maketx [\#3886](https://github.com/pypeclub/OpenPype/pull/3886) +- TrayPublisher: added persisting of last selected project [\#3871](https://github.com/pypeclub/OpenPype/pull/3871) +- TrayPublisher: added text filter on project name to Tray Publisher [\#3867](https://github.com/pypeclub/OpenPype/pull/3867) - Github issues adding `running version` section [\#3864](https://github.com/pypeclub/OpenPype/pull/3864) - Publisher: Increase size of main window [\#3862](https://github.com/pypeclub/OpenPype/pull/3862) +- Photoshop: synchronize image version with workfile [\#3854](https://github.com/pypeclub/OpenPype/pull/3854) +- General: Simple script for getting license information about used packages [\#3843](https://github.com/pypeclub/OpenPype/pull/3843) - Houdini: Increment current file on workfile publish [\#3840](https://github.com/pypeclub/OpenPype/pull/3840) - Publisher: Add new publisher to host tools [\#3833](https://github.com/pypeclub/OpenPype/pull/3833) - General: lock task workfiles when they are working on [\#3810](https://github.com/pypeclub/OpenPype/pull/3810) @@ -28,11 +33,10 @@ - Hiero: Use new Extractor location [\#3851](https://github.com/pypeclub/OpenPype/pull/3851) - Maya: Remove old legacy \(ftrack\) plug-ins that are of no use anymore [\#3819](https://github.com/pypeclub/OpenPype/pull/3819) - Nuke: Use new Extractor location [\#3799](https://github.com/pypeclub/OpenPype/pull/3799) -- Maya: Use new Extractor location [\#3775](https://github.com/pypeclub/OpenPype/pull/3775) -- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) **Merged pull requests:** +- Maya: RenderSettings set default image format for V-Ray+Redshift to exr [\#3879](https://github.com/pypeclub/OpenPype/pull/3879) - Remove lockfile during publish [\#3874](https://github.com/pypeclub/OpenPype/pull/3874) ## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) @@ -51,7 +55,6 @@ - Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) - SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) - Kitsu: Drop 'entities root' setting. [\#3739](https://github.com/pypeclub/OpenPype/pull/3739) -- git: update gitignore [\#3722](https://github.com/pypeclub/OpenPype/pull/3722) **🐛 Bug fixes** @@ -71,16 +74,18 @@ - Photoshop: Use new Extractor location [\#3789](https://github.com/pypeclub/OpenPype/pull/3789) - Blender: Use new Extractor location [\#3787](https://github.com/pypeclub/OpenPype/pull/3787) - AfterEffects: Use new Extractor location [\#3784](https://github.com/pypeclub/OpenPype/pull/3784) +- Maya: Use new Extractor location [\#3775](https://github.com/pypeclub/OpenPype/pull/3775) - General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) - General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) - General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) - General: Move create project folders to pipeline [\#3768](https://github.com/pypeclub/OpenPype/pull/3768) - General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) - Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) +- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) -- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) - Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) +- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) - Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) **Merged pull requests:** @@ -95,7 +100,6 @@ **🚀 Enhancements** - General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) -- Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) **🐛 Bug fixes** @@ -103,29 +107,21 @@ - General: Smaller fixes of imports [\#3748](https://github.com/pypeclub/OpenPype/pull/3748) - General: Logger tweaks [\#3741](https://github.com/pypeclub/OpenPype/pull/3741) - Nuke: missing job dependency if multiple bake streams [\#3737](https://github.com/pypeclub/OpenPype/pull/3737) -- Nuke: color-space settings from anatomy is working [\#3721](https://github.com/pypeclub/OpenPype/pull/3721) -- Settings: Fix studio default anatomy save [\#3716](https://github.com/pypeclub/OpenPype/pull/3716) **🔀 Refactored code** - General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) -- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) - AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) - Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) - AfterEffects: Define AfterEffects as module [\#3728](https://github.com/pypeclub/OpenPype/pull/3728) - General: Replace PypeLogger with Logger [\#3725](https://github.com/pypeclub/OpenPype/pull/3725) - Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) -- General: Move subset name functionality [\#3723](https://github.com/pypeclub/OpenPype/pull/3723) -- General: Move creators plugin getter [\#3714](https://github.com/pypeclub/OpenPype/pull/3714) - -**Merged pull requests:** - -- Hiero: Define hiero as module [\#3717](https://github.com/pypeclub/OpenPype/pull/3717) ## [3.14.0](https://github.com/pypeclub/OpenPype/tree/3.14.0) (2022-08-18) diff --git a/openpype/version.py b/openpype/version.py index a2335b696b..26b145f1db 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.3-nightly.2" +__version__ = "3.14.3-nightly.3" From dc920e1a035d1140bf34491810ab79ffb12b5604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 21 Sep 2022 13:15:18 +0200 Subject: [PATCH 476/513] Update openpype/hosts/flame/hooks/pre_flame_setup.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/flame/hooks/pre_flame_setup.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 9c2ad709c7..218fecfd2c 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -126,12 +126,14 @@ class FlamePrelaunch(PreLaunchHook): for dirtm in dirs_to_modify: for root, dirs, files in os.walk(dirtm): try: - for d in dirs: - os.chmod(os.path.join(root, d), self.permisisons) - for f in files: - os.chmod(os.path.join(root, f), self.permisisons) - except OSError as _E: - self.log.warning("Not able to open files: {}".format(_E)) + for name in set(dirs) | set(files): + path = os.path.join(root, name) + st = os.stat(path) + if oct(st.st_mode) != self.permissions: + os.chmod(path, self.permisisons) + + except OSError as exc: + self.log.warning("Not able to open files: {}".format(exc)) def _get_flame_fps(self, fps_num): From 1ec87dde38e844de4820f5aac44dd54f0b79a373 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 21 Sep 2022 13:28:08 +0200 Subject: [PATCH 477/513] typo --- openpype/hosts/flame/hooks/pre_flame_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 218fecfd2c..2dc11d94ae 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -129,7 +129,7 @@ class FlamePrelaunch(PreLaunchHook): for name in set(dirs) | set(files): path = os.path.join(root, name) st = os.stat(path) - if oct(st.st_mode) != self.permissions: + if oct(st.st_mode) != self.permisisons: os.chmod(path, self.permisisons) except OSError as exc: From f7033e716e2b078be8aff7b9cbc1dfc2d539589d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 21 Sep 2022 13:29:32 +0200 Subject: [PATCH 478/513] typo finally correct spelling --- openpype/hosts/flame/hooks/pre_flame_setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 2dc11d94ae..0173eb8e3b 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -22,7 +22,7 @@ class FlamePrelaunch(PreLaunchHook): in environment var FLAME_SCRIPT_DIR. """ app_groups = ["flame"] - permisisons = 0o777 + permissions = 0o777 wtc_script_path = os.path.join( opflame.HOST_DIR, "api", "scripts", "wiretap_com.py") @@ -129,8 +129,8 @@ class FlamePrelaunch(PreLaunchHook): for name in set(dirs) | set(files): path = os.path.join(root, name) st = os.stat(path) - if oct(st.st_mode) != self.permisisons: - os.chmod(path, self.permisisons) + if oct(st.st_mode) != self.permissions: + os.chmod(path, self.permissions) except OSError as exc: self.log.warning("Not able to open files: {}".format(exc)) From 73e05fba071d4e43ea8cd71e0682c0bd16f48b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 21 Sep 2022 14:10:56 +0200 Subject: [PATCH 479/513] Update openpype/hosts/flame/plugins/create/create_shot_clip.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/flame/plugins/create/create_shot_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 835201cd3b..a16e8d394f 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -23,7 +23,7 @@ class CreateShotClip(opfapi.Creator): # nested dictionary (only one level allowed # for sections and dict) for _k, _v in v["value"].items(): - if presets.get(_k, None) is not None: + if presets.get(_k) is not None: gui_inputs[k][ "value"][_k]["value"] = presets[_k] From 764c844db8daa8465bbe4c8ff85006cec51039d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 21 Sep 2022 14:11:04 +0200 Subject: [PATCH 480/513] Update openpype/hosts/flame/plugins/create/create_shot_clip.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/flame/plugins/create/create_shot_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index a16e8d394f..4fb041a4b2 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -27,7 +27,7 @@ class CreateShotClip(opfapi.Creator): gui_inputs[k][ "value"][_k]["value"] = presets[_k] - if presets.get(k, None) is not None: + if presets.get(k) is not None: gui_inputs[k]["value"] = presets[k] # open widget for plugins inputs From a317c8f5fd3766eddf11cc414643eff5fc975def Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Sep 2022 15:21:10 +0200 Subject: [PATCH 481/513] OP-3943 - fix broken Settins schema --- .../schemas/projects_schema/schema_project_photoshop.json | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 6668406336..b768db30ee 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -145,6 +145,7 @@ ] }, { + "type": "dict", "key": "CollectVersion", "label": "Collect Version", "children": [ From 86a7eb91e15be069193755b1bd4392f6486ccafe Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Sep 2022 15:23:27 +0200 Subject: [PATCH 482/513] Fix - updated missed import after refactor Error would occur in Webpublisher. --- openpype/pype_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index f65d969c53..d08a812c61 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -218,7 +218,7 @@ class PypeCommands: RuntimeError: When there is no path to process. """ - from openpype.hosts.webpublisher.cli_functions import ( + from openpype.hosts.webpublisher.publish_functions import ( cli_publish ) From bae5a0799b1e220f1b6b6f7ab3deb8ba3a422331 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 10:37:06 +0200 Subject: [PATCH 483/513] fix import of RepairAction --- .../hosts/houdini/plugins/publish/collect_remote_publish.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_remote_publish.py b/openpype/hosts/houdini/plugins/publish/collect_remote_publish.py index c635a53074..d56d389be0 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_remote_publish.py +++ b/openpype/hosts/houdini/plugins/publish/collect_remote_publish.py @@ -1,7 +1,7 @@ import pyblish.api -import openpype.api import hou +from openpype.pipeline.publish import RepairAction from openpype.hosts.houdini.api import lib @@ -13,7 +13,7 @@ class CollectRemotePublishSettings(pyblish.api.ContextPlugin): hosts = ["houdini"] targets = ["deadline"] label = "Remote Publish Submission Settings" - actions = [openpype.api.RepairAction] + actions = [RepairAction] def process(self, context): From c35dde88ee2ce3e5522010fd44838722d7bbd2c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 10:49:01 +0200 Subject: [PATCH 484/513] kix kwarg in hiero representations query --- openpype/hosts/hiero/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index e288cea2b1..7c27f2ebdc 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -1089,7 +1089,7 @@ def check_inventory_versions(track_items=None): # Find representations based on found containers repre_docs = get_representations( project_name, - repre_ids=repre_ids, + representation_ids=repre_ids, fields=["_id", "parent"] ) # Store representations by id and collect version ids From d1f77b7383029a3269583803258adb946d44a8a2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Sep 2022 10:57:32 +0200 Subject: [PATCH 485/513] multichannel openclip create --- openpype/hosts/flame/api/plugin.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 145b1f0921..a76e5ccc84 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -751,17 +751,18 @@ class OpenClipSolver(flib.MediaInfoFile): self.log.info("Building new openClip") self.log.debug(">> self.clip_data: {}".format(self.clip_data)) - # clip data comming from MediaInfoFile - tmp_xml_feeds = self.clip_data.find('tracks/track/feeds') - tmp_xml_feeds.set('currentVersion', self.feed_version_name) - for tmp_feed in tmp_xml_feeds: - tmp_feed.set('vuid', self.feed_version_name) + for tmp_xml_track in self.clip_data.iter("track"): + tmp_xml_feeds = tmp_xml_track.find('feeds') + tmp_xml_feeds.set('currentVersion', self.feed_version_name) - # add colorspace if any is set - if self.feed_colorspace: - self._add_colorspace(tmp_feed, self.feed_colorspace) + for tmp_feed in tmp_xml_track.iter("feed"): + tmp_feed.set('vuid', self.feed_version_name) - self._clear_handler(tmp_feed) + # add colorspace if any is set + if self.feed_colorspace: + self._add_colorspace(tmp_feed, self.feed_colorspace) + + self._clear_handler(tmp_feed) tmp_xml_versions_obj = self.clip_data.find('versions') tmp_xml_versions_obj.set('currentVersion', self.feed_version_name) @@ -812,6 +813,7 @@ class OpenClipSolver(flib.MediaInfoFile): feed_added = False if not self._feed_exists(out_xml, new_path): + tmp_xml_feed.set('vuid', self.feed_version_name) # Append new temp file feed to .clip source out xml out_track = out_xml.find("tracks/track") From 2502cb8f59fe4ed4a1e3a9c7a9a05db6d141035b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 11:08:15 +0200 Subject: [PATCH 486/513] added 'lifetime_data' to instance object --- openpype/pipeline/create/context.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index eaaed39357..070e0fb2c2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -404,6 +404,9 @@ class CreatedInstance: # Instance members may have actions on them self._members = [] + # Data that can be used for lifetime of object + self._lifetime_data = {} + # Create a copy of passed data to avoid changing them on the fly data = copy.deepcopy(data or {}) # Store original value of passed data @@ -596,6 +599,26 @@ class CreatedInstance: return self + @property + def lifetime_data(self): + """Data stored for lifetime of instance object. + + These data are not stored to scene and will be lost on object + deletion. + + Can be used to store objects. In some host implementations is not + possible to reference to object in scene with some unique identifier + (e.g. node in Fusion.). In that case it is handy to store the object + here. Should be used that way only if instance data are stored on the + node itself. + + Returns: + Dict[str, Any]: Dictionary object where you can store data related + to instance for lifetime of instance object. + """ + + return self._lifetime_data + def changes(self): """Calculate and return changes.""" From 5b26d07624b1369a280a99c8211ad5d5dddbf2bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 11:17:23 +0200 Subject: [PATCH 487/513] added 'apply_settings' method to creators so they don't have to override '__init__' --- openpype/pipeline/create/creator_plugins.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index bf2fdd2c5f..5b0532c60a 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -81,6 +81,13 @@ class BaseCreator: # - we may use UI inside processing this attribute should be checked self.headless = headless + self.apply_settings(project_settings, system_settings) + + def apply_settings(self, project_settings, system_settings): + """Method called on initialization of plugin to apply settings.""" + + pass + @property def identifier(self): """Identifier of creator (must be unique). From 316a8efeb1f85908b6f07c61dfc7f830898f9bba Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 11:17:28 +0200 Subject: [PATCH 488/513] changed imports --- openpype/pipeline/create/context.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 070e0fb2c2..9b7b6f8903 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -7,6 +7,10 @@ from uuid import uuid4 from contextlib import contextmanager from openpype.client import get_assets +from openpype.settings import ( + get_system_settings, + get_project_settings +) from openpype.host import INewPublisher from openpype.pipeline import legacy_io from openpype.pipeline.mongodb import ( @@ -20,11 +24,6 @@ from .creator_plugins import ( discover_creator_plugins, ) -from openpype.api import ( - get_system_settings, - get_project_settings -) - UpdateData = collections.namedtuple("UpdateData", ["instance", "changes"]) @@ -402,6 +401,7 @@ class CreatedInstance: self.creator = creator # Instance members may have actions on them + # TODO implement members logic self._members = [] # Data that can be used for lifetime of object From 09d7617c7344add9c9035cf4fd39615fcb9fbec0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 11:19:33 +0200 Subject: [PATCH 489/513] renamed 'INewPublisher' to 'IPublishHost' --- openpype/host/__init__.py | 2 ++ openpype/host/interfaces.py | 10 +++++++--- openpype/hosts/traypublisher/api/pipeline.py | 4 ++-- openpype/pipeline/create/context.py | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/host/__init__.py b/openpype/host/__init__.py index 519888fce3..da1237c739 100644 --- a/openpype/host/__init__.py +++ b/openpype/host/__init__.py @@ -5,6 +5,7 @@ from .host import ( from .interfaces import ( IWorkfileHost, ILoadHost, + IPublishHost, INewPublisher, ) @@ -16,6 +17,7 @@ __all__ = ( "IWorkfileHost", "ILoadHost", + "IPublishHost", "INewPublisher", "HostDirmap", diff --git a/openpype/host/interfaces.py b/openpype/host/interfaces.py index cbf12b0d13..e9008262c8 100644 --- a/openpype/host/interfaces.py +++ b/openpype/host/interfaces.py @@ -282,7 +282,7 @@ class IWorkfileHost: return self.workfile_has_unsaved_changes() -class INewPublisher: +class IPublishHost: """Functions related to new creation system in new publisher. New publisher is not storing information only about each created instance @@ -306,7 +306,7 @@ class INewPublisher: workflow. """ - if isinstance(host, INewPublisher): + if isinstance(host, IPublishHost): return [] required = [ @@ -330,7 +330,7 @@ class INewPublisher: MissingMethodsError: If there are missing methods on host implementation. """ - missing = INewPublisher.get_missing_publish_methods(host) + missing = IPublishHost.get_missing_publish_methods(host) if missing: raise MissingMethodsError(host, missing) @@ -368,3 +368,7 @@ class INewPublisher: """ pass + + +class INewPublisher(IPublishHost): + pass diff --git a/openpype/hosts/traypublisher/api/pipeline.py b/openpype/hosts/traypublisher/api/pipeline.py index 2d9db7801e..0a8ddaa343 100644 --- a/openpype/hosts/traypublisher/api/pipeline.py +++ b/openpype/hosts/traypublisher/api/pipeline.py @@ -9,7 +9,7 @@ from openpype.pipeline import ( register_creator_plugin_path, legacy_io, ) -from openpype.host import HostBase, INewPublisher +from openpype.host import HostBase, IPublishHost ROOT_DIR = os.path.dirname(os.path.dirname( @@ -19,7 +19,7 @@ PUBLISH_PATH = os.path.join(ROOT_DIR, "plugins", "publish") CREATE_PATH = os.path.join(ROOT_DIR, "plugins", "create") -class TrayPublisherHost(HostBase, INewPublisher): +class TrayPublisherHost(HostBase, IPublishHost): name = "traypublisher" def install(self): diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9b7b6f8903..a1b11d08c5 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -11,7 +11,7 @@ from openpype.settings import ( get_system_settings, get_project_settings ) -from openpype.host import INewPublisher +from openpype.host import IPublishHost from openpype.pipeline import legacy_io from openpype.pipeline.mongodb import ( AvalonMongoDB, @@ -794,7 +794,7 @@ class CreateContext: """ missing = set( - INewPublisher.get_missing_publish_methods(host) + IPublishHost.get_missing_publish_methods(host) ) return missing From 6ca895906d1eafff6f8157f8f18aa42294086e6b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 11:26:33 +0200 Subject: [PATCH 490/513] added docstring to 'INewPublisher' --- openpype/host/interfaces.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/host/interfaces.py b/openpype/host/interfaces.py index e9008262c8..cfd089a0ad 100644 --- a/openpype/host/interfaces.py +++ b/openpype/host/interfaces.py @@ -371,4 +371,14 @@ class IPublishHost: class INewPublisher(IPublishHost): + """Legacy interface replaced by 'IPublishHost'. + + Deprecated: + 'INewPublisher' is replaced by 'IPublishHost' please change your + imports. + There is no "reasonable" way hot mark these classes as deprecated + to show warning of wrong import. Deprecated since 3.14.* will be + removed in 3.15.* + """ + pass From c4127208d24d58b048773f16465d6ad208f3a35e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Sep 2022 11:29:01 +0200 Subject: [PATCH 491/513] Fix typo in ContainersFilterResult namedtuple `not_foud` -> `not_found` --- openpype/pipeline/load/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index 83b904e4a7..363120600c 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -37,7 +37,7 @@ log = logging.getLogger(__name__) ContainersFilterResult = collections.namedtuple( "ContainersFilterResult", - ["latest", "outdated", "not_foud", "invalid"] + ["latest", "outdated", "not_found", "invalid"] ) @@ -808,7 +808,7 @@ def filter_containers(containers, project_name): Categories are 'latest', 'outdated', 'invalid' and 'not_found'. The 'lastest' containers are from last version, 'outdated' are not, - 'invalid' are invalid containers (invalid content) and 'not_foud' has + 'invalid' are invalid containers (invalid content) and 'not_found' has some missing entity in database. Args: From 46c2a354f65569b9a7944d8120b41cb3e7536f4f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 11:29:12 +0200 Subject: [PATCH 492/513] publish instance has access to lifetime data --- openpype/plugins/publish/collect_from_create_context.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index 9236c698ed..b5e3225c34 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -25,7 +25,9 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): for created_instance in create_context.instances: instance_data = created_instance.data_to_store() if instance_data["active"]: - self.create_instance(context, instance_data) + self.create_instance( + context, instance_data, created_instance.lifetime_data + ) # Update global data to context context.data.update(create_context.context_data_to_store()) @@ -37,7 +39,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): legacy_io.Session[key] = value os.environ[key] = value - def create_instance(self, context, in_data): + def create_instance(self, context, in_data, lifetime_data): subset = in_data["subset"] # If instance data already contain families then use it instance_families = in_data.get("families") or [] @@ -56,5 +58,8 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): for key, value in in_data.items(): if key not in instance.data: instance.data[key] = value + + instance.data["lifetimeData"] = lifetime_data + self.log.info("collected instance: {}".format(instance.data)) self.log.info("parsing data: {}".format(in_data)) From 806865e7d7d45639cb5c7948a697490b21680d0a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 11:42:15 +0200 Subject: [PATCH 493/513] refactor 'check_inventory_versions' to use 'filter_containers' from load utils --- openpype/hosts/hiero/api/lib.py | 71 ++++++++------------------------- 1 file changed, 16 insertions(+), 55 deletions(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 7c27f2ebdc..895e95e0c0 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -13,14 +13,10 @@ import hiero from Qt import QtWidgets -from openpype.client import ( - get_project, - get_versions, - get_last_versions, - get_representations, -) +from openpype.client import get_project from openpype.settings import get_anatomy_settings from openpype.pipeline import legacy_io, Anatomy +from openpype.pipeline.load import filter_containers from openpype.lib import Logger from . import tags @@ -1055,6 +1051,10 @@ def sync_clip_name_to_data_asset(track_items_list): print("asset was changed in clip: {}".format(ti_name)) +def set_track_color(track_item, color): + track_item.source().binItem().setColor(color) + + def check_inventory_versions(track_items=None): """ Actual version color idetifier of Loaded containers @@ -1066,68 +1066,29 @@ def check_inventory_versions(track_items=None): """ from . import parse_container - track_item = track_items or get_track_items() + track_items = track_items or get_track_items() # presets clip_color_last = "green" clip_color = "red" - item_with_repre_id = [] - repre_ids = set() + containers = [] # Find all containers and collect it's node and representation ids - for track_item in track_item: + for track_item in track_items: container = parse_container(track_item) if container: - repre_id = container["representation"] - repre_ids.add(repre_id) - item_with_repre_id.append((track_item, repre_id)) + containers.append(container) # Skip if nothing was found - if not repre_ids: + if not containers: return project_name = legacy_io.active_project() - # Find representations based on found containers - repre_docs = get_representations( - project_name, - representation_ids=repre_ids, - fields=["_id", "parent"] - ) - # Store representations by id and collect version ids - repre_docs_by_id = {} - version_ids = set() - for repre_doc in repre_docs: - # Use stringed representation id to match value in containers - repre_id = str(repre_doc["_id"]) - repre_docs_by_id[repre_id] = repre_doc - version_ids.add(repre_doc["parent"]) + filter_result = filter_containers(containers, project_name) + for container in filter_result.latest: + set_track_color(container["_track_item"], clip_color) - version_docs = get_versions( - project_name, version_ids, fields=["_id", "name", "parent"] - ) - # Store versions by id and collect subset ids - version_docs_by_id = {} - subset_ids = set() - for version_doc in version_docs: - version_docs_by_id[version_doc["_id"]] = version_doc - subset_ids.add(version_doc["parent"]) - - # Query last versions based on subset ids - last_versions_by_subset_id = get_last_versions( - project_name, subset_ids=subset_ids, fields=["_id", "parent"] - ) - - for item in item_with_repre_id: - # Some python versions of nuke can't unfold tuple in for loop - track_item, repre_id = item - - repre_doc = repre_docs_by_id[repre_id] - version_doc = version_docs_by_id[repre_doc["parent"]] - last_version_doc = last_versions_by_subset_id[version_doc["parent"]] - # Check if last version is same as current version - if version_doc["_id"] == last_version_doc["_id"]: - track_item.source().binItem().setColor(clip_color_last) - else: - track_item.source().binItem().setColor(clip_color) + for container in filter_result.outdated: + set_track_color(container["_track_item"], clip_color_last) def selection_changed_timeline(event): From fb121844300e904038d33ad4b34989a6244b6a6c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Sep 2022 12:17:40 +0200 Subject: [PATCH 494/513] updating multichannel openclip --- openpype/hosts/flame/api/plugin.py | 105 +++++++++++++++++++---------- 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index a76e5ccc84..9f09fa13ce 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -775,6 +775,15 @@ class OpenClipSolver(flib.MediaInfoFile): self.write_clip_data_to_file(self.out_file, self.clip_data) + def _get_xml_track_obj_by_uid(self, xml_data, uid): + # loop all tracks of input xml data + for xml_track in xml_data.iter("track"): + track_uid = xml_track.get("uid") + + # get matching uids + if uid == track_uid: + return xml_track + def _update_open_clip(self): self.log.info("Updating openClip ..") @@ -784,53 +793,75 @@ class OpenClipSolver(flib.MediaInfoFile): self.log.debug(">> out_xml: {}".format(out_xml)) self.log.debug(">> self.clip_data: {}".format(self.clip_data)) - # Get new feed from tmp file - tmp_xml_feed = self.clip_data.find('tracks/track/feeds/feed') + # loop tmp tracks + updated_any = [] + for tmp_xml_track in self.clip_data.iter("track"): + # get tmp track uid + tmp_track_uid = tmp_xml_track.get("uid") + # get out data track by uid + out_track_element = self._get_xml_track_obj_by_uid( + out_xml, tmp_track_uid) - self._clear_handler(tmp_xml_feed) + # loop tmp feeds + for tmp_xml_feed in tmp_xml_track.iter("feed"): + new_path_obj = tmp_xml_feed.find( + "spans/span/path") + new_path = new_path_obj.text - # update fps from MediaInfoFile class - if self.fps: - tmp_feed_fps_obj = tmp_xml_feed.find( - "startTimecode/rate") - tmp_feed_fps_obj.text = str(self.fps) + # check if feed path already exists in track's feeds + if ( + out_track_element + and not self._feed_exists(out_track_element, new_path) + ): + continue - # update start_frame from MediaInfoFile class - if self.start_frame: - tmp_feed_nb_ticks_obj = tmp_xml_feed.find( - "startTimecode/nbTicks") - tmp_feed_nb_ticks_obj.text = str(self.start_frame) + # rename versions on feeds + tmp_xml_feed.set('vuid', self.feed_version_name) + self._clear_handler(tmp_xml_feed) - # update drop_mode from MediaInfoFile class - if self.drop_mode: - tmp_feed_drop_mode_obj = tmp_xml_feed.find( - "startTimecode/dropMode") - tmp_feed_drop_mode_obj.text = str(self.drop_mode) + # update fps from MediaInfoFile class + if self.fps: + tmp_feed_fps_obj = tmp_xml_feed.find( + "startTimecode/rate") + tmp_feed_fps_obj.text = str(self.fps) - new_path_obj = tmp_xml_feed.find( - "spans/span/path") - new_path = new_path_obj.text + # update start_frame from MediaInfoFile class + if self.start_frame: + tmp_feed_nb_ticks_obj = tmp_xml_feed.find( + "startTimecode/nbTicks") + tmp_feed_nb_ticks_obj.text = str(self.start_frame) - feed_added = False - if not self._feed_exists(out_xml, new_path): + # update drop_mode from MediaInfoFile class + if self.drop_mode: + tmp_feed_drop_mode_obj = tmp_xml_feed.find( + "startTimecode/dropMode") + tmp_feed_drop_mode_obj.text = str(self.drop_mode) - tmp_xml_feed.set('vuid', self.feed_version_name) - # Append new temp file feed to .clip source out xml - out_track = out_xml.find("tracks/track") - # add colorspace if any is set - if self.feed_colorspace: - self._add_colorspace(tmp_xml_feed, self.feed_colorspace) + # add colorspace if any is set + if self.feed_colorspace: + self._add_colorspace(tmp_xml_feed, self.feed_colorspace) - out_feeds = out_track.find('feeds') - out_feeds.set('currentVersion', self.feed_version_name) - out_feeds.append(tmp_xml_feed) + # then append/update feed to correct track in output + if out_track_element: + # update already present track + out_feeds = out_track_element.find('feeds') + out_feeds.set('currentVersion', self.feed_version_name) + out_feeds.append(tmp_xml_feed) - self.log.info( - "Appending new feed: {}".format( - self.feed_version_name)) - feed_added = True + self.log.info( + "Appending new feed: {}".format( + self.feed_version_name)) + else: + # create new track as it doesnt exists yet + # set current version to feeds on tmp + tmp_xml_feeds = tmp_xml_track.find('feeds') + tmp_xml_feeds.set('currentVersion', self.feed_version_name) + out_tracks = out_xml.find("tracks") + out_tracks.append(tmp_xml_track) - if feed_added: + updated_any.append(True) + + if any(updated_any): # Append vUID to versions out_xml_versions_obj = out_xml.find('versions') out_xml_versions_obj.set( From fa3409d30258ba9dcbaec48cec0995e494560778 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Sep 2022 12:37:49 +0200 Subject: [PATCH 495/513] fix logging in plugin --- openpype/hosts/flame/api/lib.py | 6 +++--- openpype/hosts/flame/api/plugin.py | 8 ++++++-- openpype/hosts/flame/plugins/load/load_clip.py | 6 +++--- openpype/hosts/flame/plugins/load/load_clip_batch.py | 5 ++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 94c46fe937..b7f7b24e51 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -766,11 +766,11 @@ class MediaInfoFile(object): _drop_mode = None _file_pattern = None - def __init__(self, path, **kwargs): + def __init__(self, path, logger=None): # replace log if any - if kwargs.get("logger"): - self.log = kwargs["logger"] + if logger: + self.log = logger # test if `dl_get_media_info` paht exists self._validate_media_script_path() diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 9f09fa13ce..205acf51b0 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -694,16 +694,20 @@ class OpenClipSolver(flib.MediaInfoFile): log = log - def __init__(self, openclip_file_path, feed_data): + def __init__(self, openclip_file_path, feed_data, logger=None): self.out_file = openclip_file_path + # replace log if any + if logger: + self.log = logger + # new feed variables: feed_path = feed_data.pop("path") # initialize parent class super(OpenClipSolver, self).__init__( feed_path, - **feed_data + logger=logger ) # get other metadata diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index b12f2f9690..0843dde76a 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -4,6 +4,7 @@ from pprint import pformat import openpype.hosts.flame.api as opfapi from openpype.lib import StringTemplate + class LoadClip(opfapi.ClipLoader): """Load a subset to timeline as clip @@ -60,8 +61,6 @@ class LoadClip(opfapi.ClipLoader): "path": self.fname.replace("\\", "/"), "colorspace": colorspace, "version": "v{:0>3}".format(version_name), - "logger": self.log - } self.log.debug(pformat( loading_context @@ -69,7 +68,8 @@ class LoadClip(opfapi.ClipLoader): self.log.debug(openclip_path) # make openpype clip file - opfapi.OpenClipSolver(openclip_path, loading_context).make() + opfapi.OpenClipSolver( + openclip_path, loading_context, logger=self.log).make() # prepare Reel group in actual desktop opc = self._get_clip( diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index fb4a3dc6e9..3b049b861b 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -64,8 +64,6 @@ class LoadClipBatch(opfapi.ClipLoader): "path": self.fname.replace("\\", "/"), "colorspace": colorspace, "version": "v{:0>3}".format(version_name), - "logger": self.log - } self.log.debug(pformat( loading_context @@ -73,7 +71,8 @@ class LoadClipBatch(opfapi.ClipLoader): self.log.debug(openclip_path) # make openpype clip file - opfapi.OpenClipSolver(openclip_path, loading_context).make() + opfapi.OpenClipSolver( + openclip_path, loading_context, logger=self.log).make() # prepare Reel group in actual desktop opc = self._get_clip( From 3ac038760645bb76c5c60c473b7aa4dc6880e292 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Sep 2022 12:45:46 +0200 Subject: [PATCH 496/513] fix future warning add logging from lib --- openpype/hosts/flame/api/plugin.py | 2 +- openpype/hosts/flame/plugins/load/load_clip_batch.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 205acf51b0..59c8dab631 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -814,7 +814,7 @@ class OpenClipSolver(flib.MediaInfoFile): # check if feed path already exists in track's feeds if ( - out_track_element + out_track_element is not None and not self._feed_exists(out_track_element, new_path) ): continue diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index 3b049b861b..c17e060f5b 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -2,7 +2,7 @@ import os import flame from pprint import pformat import openpype.hosts.flame.api as opfapi -from openpype.lib import StringTemplate +from openpype.lib import StringTemplate, Logger class LoadClipBatch(opfapi.ClipLoader): @@ -24,6 +24,8 @@ class LoadClipBatch(opfapi.ClipLoader): reel_name = "OP_LoadedReel" clip_name_template = "{asset}_{subset}<_{output}>" + log = Logger.get_logger(__file__) + def load(self, context, name, namespace, options): # get flame objects From 7e35fdcdedf583c8a58ff9cbb93c9067189149dd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Sep 2022 12:52:08 +0200 Subject: [PATCH 497/513] abstracting logger --- openpype/hosts/flame/api/plugin.py | 1 + openpype/hosts/flame/plugins/load/load_clip_batch.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 59c8dab631..a3f2f5f2fc 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -678,6 +678,7 @@ class ClipLoader(LoaderPlugin): `update` logic. """ + log = log options = [ qargparse.Boolean( diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index c17e060f5b..3b049b861b 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -2,7 +2,7 @@ import os import flame from pprint import pformat import openpype.hosts.flame.api as opfapi -from openpype.lib import StringTemplate, Logger +from openpype.lib import StringTemplate class LoadClipBatch(opfapi.ClipLoader): @@ -24,8 +24,6 @@ class LoadClipBatch(opfapi.ClipLoader): reel_name = "OP_LoadedReel" clip_name_template = "{asset}_{subset}<_{output}>" - log = Logger.get_logger(__file__) - def load(self, context, name, namespace, options): # get flame objects From 7063d21450607bf07bd430af06fef517044338a3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Sep 2022 12:58:20 +0200 Subject: [PATCH 498/513] adding logging --- openpype/hosts/flame/api/plugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index a3f2f5f2fc..044e86b17f 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -784,6 +784,8 @@ class OpenClipSolver(flib.MediaInfoFile): # loop all tracks of input xml data for xml_track in xml_data.iter("track"): track_uid = xml_track.get("uid") + self.log.debug( + ">> track_uid:uid: {}:{}".format(track_uid, uid)) # get matching uids if uid == track_uid: @@ -803,9 +805,13 @@ class OpenClipSolver(flib.MediaInfoFile): for tmp_xml_track in self.clip_data.iter("track"): # get tmp track uid tmp_track_uid = tmp_xml_track.get("uid") + self.log.debug(">> tmp_track_uid: {}".format(tmp_track_uid)) + # get out data track by uid out_track_element = self._get_xml_track_obj_by_uid( out_xml, tmp_track_uid) + self.log.debug( + ">> out_track_element: {}".format(out_track_element)) # loop tmp feeds for tmp_xml_feed in tmp_xml_track.iter("feed"): @@ -848,6 +854,7 @@ class OpenClipSolver(flib.MediaInfoFile): # then append/update feed to correct track in output if out_track_element: + self.log.debug("updating track element ..") # update already present track out_feeds = out_track_element.find('feeds') out_feeds.set('currentVersion', self.feed_version_name) @@ -857,6 +864,7 @@ class OpenClipSolver(flib.MediaInfoFile): "Appending new feed: {}".format( self.feed_version_name)) else: + self.log.debug("adding new track element ..") # create new track as it doesnt exists yet # set current version to feeds on tmp tmp_xml_feeds = tmp_xml_track.find('feeds') From ca7300a95fd1db17ef6656c6884be7157627c8f8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Sep 2022 13:41:01 +0200 Subject: [PATCH 499/513] fixing logic --- openpype/hosts/flame/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 044e86b17f..6ad33f16a3 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -822,7 +822,7 @@ class OpenClipSolver(flib.MediaInfoFile): # check if feed path already exists in track's feeds if ( out_track_element is not None - and not self._feed_exists(out_track_element, new_path) + and self._feed_exists(out_track_element, new_path) ): continue From 54d5724d6ac1ad7478e4df61de10e33833c1dba9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Sep 2022 11:59:06 +0200 Subject: [PATCH 500/513] Fix log formatting (global file format and aov file format were previously swapped) --- openpype/hosts/maya/plugins/publish/validate_rendersettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 7f0985f69b..bfb72c7012 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -201,7 +201,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): labels = get_redshift_image_format_labels() cls.log.error( "AOV file format {} does not match global file format " - "{}".format(labels[default_ext], labels[aov_ext]) + "{}".format(labels[aov_ext], labels[default_ext]) ) invalid = True From da7d4cb1d7d792ae4c2398d64bdd3e1d9036d81c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 23 Sep 2022 12:57:17 +0200 Subject: [PATCH 501/513] OP-3682 - updates to match to v4 payload Parsing should match payload from localhost:5000/api/addons?details=1 --- .../distribution/addon_distribution.py | 29 ++++- .../tests/test_addon_distributtion.py | 104 +++++++++++++----- 2 files changed, 98 insertions(+), 35 deletions(-) diff --git a/common/openpype_common/distribution/addon_distribution.py b/common/openpype_common/distribution/addon_distribution.py index ad17a831d8..fec8cb762b 100644 --- a/common/openpype_common/distribution/addon_distribution.py +++ b/common/openpype_common/distribution/addon_distribution.py @@ -44,12 +44,18 @@ class WebAddonSource(AddonSource): url = attr.ib(default=None) +@attr.s +class VersionData(object): + version_data = attr.ib(default=None) + + @attr.s class AddonInfo(object): """Object matching json payload from Server""" name = attr.ib() version = attr.ib() - sources = attr.ib(default=attr.Factory(list)) + title = attr.ib(default=None) + sources = attr.ib(default=attr.Factory(dict)) hash = attr.ib(default=None) description = attr.ib(default=None) license = attr.ib(default=None) @@ -58,7 +64,16 @@ class AddonInfo(object): @classmethod def from_dict(cls, data): sources = [] - for source in data.get("sources", []): + + production_version = data.get("productionVersion") + if not production_version: + return + + # server payload contains info about all versions + # active addon must have 'productionVersion' and matching version info + version_data = data.get("versions", {})[production_version] + + for source in version_data.get("clientSourceInfo", []): if source.get("type") == UrlType.FILESYSTEM.value: source_addon = LocalAddonSource(type=source["type"], path=source["path"]) @@ -69,10 +84,11 @@ class AddonInfo(object): sources.append(source_addon) return cls(name=data.get("name"), - version=data.get("version"), + version=production_version, + sources=sources, hash=data.get("hash"), description=data.get("description"), - sources=sources, + title=data.get("title"), license=data.get("license"), authors=data.get("authors")) @@ -228,8 +244,9 @@ def update_addon_state(addon_infos, destination_folder, factory, for source in addon.sources: download_states[full_name] = UpdateState.FAILED.value try: - downloader = factory.get_downloader(source["type"]) - zip_file_path = downloader.download(source, addon_dest) + downloader = factory.get_downloader(source.type) + zip_file_path = downloader.download(attr.asdict(source), + addon_dest) downloader.check_hash(zip_file_path, addon.hash) downloader.unzip(zip_file_path, addon_dest) download_states[full_name] = UpdateState.UPDATED.value diff --git a/common/openpype_common/distribution/tests/test_addon_distributtion.py b/common/openpype_common/distribution/tests/test_addon_distributtion.py index faf4e01e22..46bcd276cd 100644 --- a/common/openpype_common/distribution/tests/test_addon_distributtion.py +++ b/common/openpype_common/distribution/tests/test_addon_distributtion.py @@ -35,23 +35,50 @@ def temp_folder(): @pytest.fixture def sample_addon_info(): addon_info = { - "name": "openpype_slack", - "version": "1.0.0", - "sources": [ - { - "type": "http", - "url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing" # noqa - }, - { - "type": "filesystem", - "path": { - "windows": ["P:/sources/some_file.zip", "W:/sources/some_file.zip"], # noqa - "linux": ["/mnt/srv/sources/some_file.zip"], - "darwin": ["/Volumes/srv/sources/some_file.zip"] - } + "versions": { + "1.0.0": { + "clientPyproject": { + "tool": { + "poetry": { + "dependencies": { + "nxtools": "^1.6", + "orjson": "^3.6.7", + "typer": "^0.4.1", + "email-validator": "^1.1.3", + "python": "^3.10", + "fastapi": "^0.73.0" + } + } + } + }, + "hasSettings": True, + "clientSourceInfo": [ + { + "type": "http", + "url": "https://drive.google.com/file/d/1TcuV8c2OV8CcbPeWi7lxOdqWsEqQNPYy/view?usp=sharing" # noqa + }, + { + "type": "filesystem", + "path": { + "windows": ["P:/sources/some_file.zip", + "W:/sources/some_file.zip"], # noqa + "linux": ["/mnt/srv/sources/some_file.zip"], + "darwin": ["/Volumes/srv/sources/some_file.zip"] + } + } + ], + "frontendScopes": { + "project": { + "sidebar": "hierarchy" + } + } } - ], - "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658" # noqa + }, + "description": "", + "title": "Slack addon", + "name": "openpype_slack", + "productionVersion": "1.0.0", + "hash": "4be25eb6215e91e5894d3c5475aeb1e379d081d3f5b43b4ee15b0891cf5f5658" # noqa } yield addon_info @@ -73,16 +100,39 @@ def test_get_downloader(printer, addon_downloader): def test_addon_info(printer, sample_addon_info): - valid_minimum = {"name": "openpype_slack", "version": "1.0.0"} + """Tests parsing of expected payload from v4 server into AadonInfo.""" + valid_minimum = { + "name": "openpype_slack", + "productionVersion": "1.0.0", + "versions": { + "1.0.0": { + "clientSourceInfo": [ + { + "type": "filesystem", + "path": { + "windows": [ + "P:/sources/some_file.zip", + "W:/sources/some_file.zip"], + "linux": [ + "/mnt/srv/sources/some_file.zip"], + "darwin": [ + "/Volumes/srv/sources/some_file.zip"] # noqa + } + } + ] + } + } + } assert AddonInfo.from_dict(valid_minimum), "Missing required fields" - assert AddonInfo(name=valid_minimum["name"], - version=valid_minimum["version"]), \ - "Missing required fields" - with pytest.raises(TypeError): - # TODO should be probably implemented - assert AddonInfo(valid_minimum), "Wrong argument format" + valid_minimum["versions"].pop("1.0.0") + with pytest.raises(KeyError): + assert not AddonInfo.from_dict(valid_minimum), "Must fail without version data" # noqa + + valid_minimum.pop("productionVersion") + assert not AddonInfo.from_dict( + valid_minimum), "none if not productionVersion" # noqa addon = AddonInfo.from_dict(sample_addon_info) assert addon, "Should be created" @@ -95,15 +145,11 @@ def test_addon_info(printer, sample_addon_info): addon_as_dict = attr.asdict(addon) assert addon_as_dict["name"], "Dict approach should work" - with pytest.raises(TypeError): - # TODO should be probably implemented as . not dict - first_source = addon.sources[0] - assert first_source["type"] == "http", "Not implemented" - def test_update_addon_state(printer, sample_addon_info, temp_folder, addon_downloader): - addon_info = AddonInfo(**sample_addon_info) + """Tests possible cases of addon update.""" + addon_info = AddonInfo.from_dict(sample_addon_info) orig_hash = addon_info.hash addon_info.hash = "brokenhash" From c659dcfce6393972aa0443f03f67950b1e9fdc45 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 23 Sep 2022 13:05:50 +0200 Subject: [PATCH 502/513] OP-3682 - extracted AddonInfo to separate file Parsing of v4 payload info will be required in Dependencies tool also. --- .../distribution/addon_distribution.py | 78 +----------------- .../distribution/addon_info.py | 80 +++++++++++++++++++ .../tests/test_addon_distributtion.py | 2 +- 3 files changed, 82 insertions(+), 78 deletions(-) create mode 100644 common/openpype_common/distribution/addon_info.py diff --git a/common/openpype_common/distribution/addon_distribution.py b/common/openpype_common/distribution/addon_distribution.py index fec8cb762b..5e48639dec 100644 --- a/common/openpype_common/distribution/addon_distribution.py +++ b/common/openpype_common/distribution/addon_distribution.py @@ -8,12 +8,7 @@ import platform import shutil from .file_handler import RemoteFileHandler - - -class UrlType(Enum): - HTTP = "http" - GIT = "git" - FILESYSTEM = "filesystem" +from .addon_info import AddonInfo class UpdateState(Enum): @@ -22,77 +17,6 @@ class UpdateState(Enum): FAILED = "failed" -@attr.s -class MultiPlatformPath(object): - windows = attr.ib(default=None) - linux = attr.ib(default=None) - darwin = attr.ib(default=None) - - -@attr.s -class AddonSource(object): - type = attr.ib() - - -@attr.s -class LocalAddonSource(AddonSource): - path = attr.ib(default=attr.Factory(MultiPlatformPath)) - - -@attr.s -class WebAddonSource(AddonSource): - url = attr.ib(default=None) - - -@attr.s -class VersionData(object): - version_data = attr.ib(default=None) - - -@attr.s -class AddonInfo(object): - """Object matching json payload from Server""" - name = attr.ib() - version = attr.ib() - title = attr.ib(default=None) - sources = attr.ib(default=attr.Factory(dict)) - hash = attr.ib(default=None) - description = attr.ib(default=None) - license = attr.ib(default=None) - authors = attr.ib(default=None) - - @classmethod - def from_dict(cls, data): - sources = [] - - production_version = data.get("productionVersion") - if not production_version: - return - - # server payload contains info about all versions - # active addon must have 'productionVersion' and matching version info - version_data = data.get("versions", {})[production_version] - - for source in version_data.get("clientSourceInfo", []): - if source.get("type") == UrlType.FILESYSTEM.value: - source_addon = LocalAddonSource(type=source["type"], - path=source["path"]) - if source.get("type") == UrlType.HTTP.value: - source_addon = WebAddonSource(type=source["type"], - url=source["url"]) - - sources.append(source_addon) - - return cls(name=data.get("name"), - version=production_version, - sources=sources, - hash=data.get("hash"), - description=data.get("description"), - title=data.get("title"), - license=data.get("license"), - authors=data.get("authors")) - - class AddonDownloader: log = logging.getLogger(__name__) diff --git a/common/openpype_common/distribution/addon_info.py b/common/openpype_common/distribution/addon_info.py new file mode 100644 index 0000000000..00ece11f3b --- /dev/null +++ b/common/openpype_common/distribution/addon_info.py @@ -0,0 +1,80 @@ +import attr +from enum import Enum + + +class UrlType(Enum): + HTTP = "http" + GIT = "git" + FILESYSTEM = "filesystem" + + +@attr.s +class MultiPlatformPath(object): + windows = attr.ib(default=None) + linux = attr.ib(default=None) + darwin = attr.ib(default=None) + + +@attr.s +class AddonSource(object): + type = attr.ib() + + +@attr.s +class LocalAddonSource(AddonSource): + path = attr.ib(default=attr.Factory(MultiPlatformPath)) + + +@attr.s +class WebAddonSource(AddonSource): + url = attr.ib(default=None) + + +@attr.s +class VersionData(object): + version_data = attr.ib(default=None) + + +@attr.s +class AddonInfo(object): + """Object matching json payload from Server""" + name = attr.ib() + version = attr.ib() + title = attr.ib(default=None) + sources = attr.ib(default=attr.Factory(dict)) + hash = attr.ib(default=None) + description = attr.ib(default=None) + license = attr.ib(default=None) + authors = attr.ib(default=None) + + @classmethod + def from_dict(cls, data): + sources = [] + + production_version = data.get("productionVersion") + if not production_version: + return + + # server payload contains info about all versions + # active addon must have 'productionVersion' and matching version info + version_data = data.get("versions", {})[production_version] + + for source in version_data.get("clientSourceInfo", []): + if source.get("type") == UrlType.FILESYSTEM.value: + source_addon = LocalAddonSource(type=source["type"], + path=source["path"]) + if source.get("type") == UrlType.HTTP.value: + source_addon = WebAddonSource(type=source["type"], + url=source["url"]) + + sources.append(source_addon) + + return cls(name=data.get("name"), + version=production_version, + sources=sources, + hash=data.get("hash"), + description=data.get("description"), + title=data.get("title"), + license=data.get("license"), + authors=data.get("authors")) + diff --git a/common/openpype_common/distribution/tests/test_addon_distributtion.py b/common/openpype_common/distribution/tests/test_addon_distributtion.py index 46bcd276cd..765ea0596a 100644 --- a/common/openpype_common/distribution/tests/test_addon_distributtion.py +++ b/common/openpype_common/distribution/tests/test_addon_distributtion.py @@ -4,13 +4,13 @@ import tempfile from common.openpype_common.distribution.addon_distribution import ( AddonDownloader, - UrlType, OSAddonDownloader, HTTPAddonDownloader, AddonInfo, update_addon_state, UpdateState ) +from common.openpype_common.distribution.addon_info import UrlType @pytest.fixture From a25b951607239984719acb550506571025682069 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 23 Sep 2022 13:12:04 +0200 Subject: [PATCH 503/513] making the code more appealing --- .../publish/extract_subset_resources.py | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 1b7e9b88b5..5482af973c 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -79,10 +79,10 @@ class ExtractSubsetResources(openpype.api.Extractor): retimed_data = self._get_retimed_attributes(instance) # get individual keys - r_handle_start = retimed_data["handle_start"] - r_handle_end = retimed_data["handle_end"] - r_source_dur = retimed_data["source_duration"] - r_speed = retimed_data["speed"] + retimed_handle_start = retimed_data["handle_start"] + retimed_handle_end = retimed_data["handle_end"] + retimed_source_duration = retimed_data["source_duration"] + retimed_speed = retimed_data["speed"] # get handles value - take only the max from both handle_start = instance.data["handleStart"] @@ -96,23 +96,23 @@ class ExtractSubsetResources(openpype.api.Extractor): source_end_handles = instance.data["sourceEndH"] # retime if needed - if r_speed != 1.0: + if retimed_speed != 1.0: if retimed_handles: # handles are retimed source_start_handles = ( - instance.data["sourceStart"] - r_handle_start) + instance.data["sourceStart"] - retimed_handle_start) source_end_handles = ( source_start_handles - + (r_source_dur - 1) - + r_handle_start - + r_handle_end + + (retimed_source_duration - 1) + + retimed_handle_start + + retimed_handle_end ) else: # handles are not retimed source_end_handles = ( source_start_handles - + (r_source_dur - 1) + + (retimed_source_duration - 1) + handle_start + handle_end ) @@ -121,11 +121,11 @@ class ExtractSubsetResources(openpype.api.Extractor): frame_start_handle = frame_start - handle_start repre_frame_start = frame_start_handle if include_handles: - if r_speed == 1.0 or not retimed_handles: + if retimed_speed == 1.0 or not retimed_handles: frame_start_handle = frame_start else: frame_start_handle = ( - frame_start - handle_start) + r_handle_start + frame_start - handle_start) + retimed_handle_start self.log.debug("_ frame_start_handle: {}".format( frame_start_handle)) @@ -163,29 +163,29 @@ class ExtractSubsetResources(openpype.api.Extractor): instance.data["versionData"].update(version_data) # version data start frame - vd_frame_start = frame_start + version_frame_start = frame_start if include_handles: - vd_frame_start = frame_start_handle - - if r_speed != 1.0: - instance.data["versionData"].update({ - "frameStart": vd_frame_start, - "frameEnd": ( - (vd_frame_start + source_duration_handles - 1) - - (r_handle_start + r_handle_end) - ) - }) - if not retimed_handles: + version_frame_start = frame_start_handle + if retimed_speed != 1.0: + if retimed_handles: + instance.data["versionData"].update({ + "frameStart": version_frame_start, + "frameEnd": ( + (version_frame_start + source_duration_handles - 1) + - (retimed_handle_start + retimed_handle_end) + ) + }) + else: instance.data["versionData"].update({ "handleStart": handle_start, "handleEnd": handle_end, - "frameStart": vd_frame_start, + "frameStart": version_frame_start, "frameEnd": ( - (vd_frame_start + source_duration_handles - 1) + (version_frame_start + source_duration_handles - 1) - (handle_start + handle_end) ) }) - self.log.debug("_ i_version_data: {}".format( + self.log.debug("_ version_data: {}".format( instance.data["versionData"] )) From 68ef0e35066c6a2eaff185665d6fd30749a164b5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 23 Sep 2022 13:23:18 +0200 Subject: [PATCH 504/513] making code more appealing --- openpype/hosts/flame/api/plugin.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 6ad33f16a3..1a26e96c79 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -801,7 +801,7 @@ class OpenClipSolver(flib.MediaInfoFile): self.log.debug(">> self.clip_data: {}".format(self.clip_data)) # loop tmp tracks - updated_any = [] + updated_any = False for tmp_xml_track in self.clip_data.iter("track"): # get tmp track uid tmp_track_uid = tmp_xml_track.get("uid") @@ -831,25 +831,25 @@ class OpenClipSolver(flib.MediaInfoFile): self._clear_handler(tmp_xml_feed) # update fps from MediaInfoFile class - if self.fps: + if self.fps is not None: tmp_feed_fps_obj = tmp_xml_feed.find( "startTimecode/rate") tmp_feed_fps_obj.text = str(self.fps) # update start_frame from MediaInfoFile class - if self.start_frame: + if self.start_frame is not None: tmp_feed_nb_ticks_obj = tmp_xml_feed.find( "startTimecode/nbTicks") tmp_feed_nb_ticks_obj.text = str(self.start_frame) # update drop_mode from MediaInfoFile class - if self.drop_mode: + if self.drop_mode is not None: tmp_feed_drop_mode_obj = tmp_xml_feed.find( "startTimecode/dropMode") tmp_feed_drop_mode_obj.text = str(self.drop_mode) # add colorspace if any is set - if self.feed_colorspace: + if self.feed_colorspace is not None: self._add_colorspace(tmp_xml_feed, self.feed_colorspace) # then append/update feed to correct track in output @@ -872,9 +872,9 @@ class OpenClipSolver(flib.MediaInfoFile): out_tracks = out_xml.find("tracks") out_tracks.append(tmp_xml_track) - updated_any.append(True) + updated_any = True - if any(updated_any): + if updated_any: # Append vUID to versions out_xml_versions_obj = out_xml.find('versions') out_xml_versions_obj.set( From 00995475d9c9471dda3923287a8295f64687d4ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 13:23:32 +0200 Subject: [PATCH 505/513] added function to get default values of attribute definitions --- openpype/lib/attribute_definitions.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index cbd53d1f07..37446f01f8 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -30,6 +30,28 @@ def get_attributes_keys(attribute_definitions): return keys +def get_default_values(attribute_definitions): + """Receive default values for attribute definitions. + + Args: + attribute_definitions (List[AbtractAttrDef]): Attribute definitions for + which default values should be collected. + + Returns: + Dict[str, Any]: Default values for passet attribute definitions. + """ + + output = {} + if not attribute_definitions: + return output + + for attr_def in attribute_definitions: + # Skip UI definitions + if not isinstance(attr_def, UIDef): + output[attr_def.key] = attr_def.default + return output + + class AbstractAttrDefMeta(ABCMeta): """Meta class to validate existence of 'key' attribute. From 2db3aa131cc0920ff6050c8bca55997cd3c50f21 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 13:24:17 +0200 Subject: [PATCH 506/513] fix typo --- openpype/hosts/nuke/api/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index 709ee3b743..7317f6726b 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -55,7 +55,7 @@ class NukeTemplateBuilder(AbstractTemplateBuilder): class NukePlaceholderPlugin(PlaceholderPlugin): - noce_color = 4278190335 + node_color = 4278190335 def _collect_scene_placeholders(self): # Cache placeholder data to shared data From d6c7509150d695b7c7e5a60d8bc13889e71b01af Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 13:27:28 +0200 Subject: [PATCH 507/513] fix 'get_load_plugin_options' in maya --- openpype/hosts/maya/api/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 9163cf9a6f..be5dc3db61 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -212,7 +212,7 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): self.populate_load_placeholder(placeholder, repre_ids) def get_placeholder_options(self, options=None): - return self.get_load_plugin_options(self, options) + return self.get_load_plugin_options(options) def cleanup_placeholder(self, placeholder): """Hide placeholder, parent them to root From 654b5744de733ebebd045a0b2614a8d40e7c41bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 14:06:39 +0200 Subject: [PATCH 508/513] fix arguments for 'cleanup_placeholder' --- openpype/hosts/maya/api/workfile_template_builder.py | 2 +- openpype/hosts/nuke/api/workfile_template_builder.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index be5dc3db61..ef043ed0f4 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -214,7 +214,7 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): def get_placeholder_options(self, options=None): return self.get_load_plugin_options(options) - def cleanup_placeholder(self, placeholder): + def cleanup_placeholder(self, placeholder, failed): """Hide placeholder, parent them to root add them to placeholder set and register placeholder's parent to keep placeholder info available for future use diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index 7317f6726b..7a2e442e32 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -192,7 +192,7 @@ class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): def get_placeholder_options(self, options=None): return self.get_load_plugin_options(options) - def cleanup_placeholder(self, placeholder): + def cleanup_placeholder(self, placeholder, failed): # deselect all selected nodes placeholder_node = nuke.toNode(placeholder.scene_identifier) From 9c1ee5b79c90fd3db25857661a39a21e97b9d5ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 23 Sep 2022 17:11:40 +0200 Subject: [PATCH 509/513] renamed 'lifetime_data' to 'transient_data' --- openpype/pipeline/create/context.py | 6 +++--- openpype/plugins/publish/collect_from_create_context.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index a1b11d08c5..a7e43cb2f2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -405,7 +405,7 @@ class CreatedInstance: self._members = [] # Data that can be used for lifetime of object - self._lifetime_data = {} + self._transient_data = {} # Create a copy of passed data to avoid changing them on the fly data = copy.deepcopy(data or {}) @@ -600,7 +600,7 @@ class CreatedInstance: return self @property - def lifetime_data(self): + def transient_data(self): """Data stored for lifetime of instance object. These data are not stored to scene and will be lost on object @@ -617,7 +617,7 @@ class CreatedInstance: to instance for lifetime of instance object. """ - return self._lifetime_data + return self._transient_data def changes(self): """Calculate and return changes.""" diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index b5e3225c34..fc0f97b187 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -26,7 +26,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): instance_data = created_instance.data_to_store() if instance_data["active"]: self.create_instance( - context, instance_data, created_instance.lifetime_data + context, instance_data, created_instance.transient_data ) # Update global data to context @@ -39,7 +39,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): legacy_io.Session[key] = value os.environ[key] = value - def create_instance(self, context, in_data, lifetime_data): + def create_instance(self, context, in_data, transient_data): subset = in_data["subset"] # If instance data already contain families then use it instance_families = in_data.get("families") or [] @@ -59,7 +59,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): if key not in instance.data: instance.data[key] = value - instance.data["lifetimeData"] = lifetime_data + instance.data["transientData"] = transient_data self.log.info("collected instance: {}".format(instance.data)) self.log.info("parsing data: {}".format(in_data)) From 50b850ec17e37b720b21d1c80e87320465d3db05 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 24 Sep 2022 04:19:03 +0000 Subject: [PATCH 510/513] [Automated] Bump version --- CHANGELOG.md | 31 ++++++++++++++----------------- openpype/version.py | 2 +- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f868e6ed6e..24e02acc6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,20 @@ # Changelog -## [3.14.3-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.3-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.2...HEAD) **🚀 Enhancements** - Maya: better logging in Maketx [\#3886](https://github.com/pypeclub/OpenPype/pull/3886) +- Photoshop: review can be turned off [\#3885](https://github.com/pypeclub/OpenPype/pull/3885) - TrayPublisher: added persisting of last selected project [\#3871](https://github.com/pypeclub/OpenPype/pull/3871) - TrayPublisher: added text filter on project name to Tray Publisher [\#3867](https://github.com/pypeclub/OpenPype/pull/3867) - Github issues adding `running version` section [\#3864](https://github.com/pypeclub/OpenPype/pull/3864) - Publisher: Increase size of main window [\#3862](https://github.com/pypeclub/OpenPype/pull/3862) +- Flame: make migratable projects after creation [\#3860](https://github.com/pypeclub/OpenPype/pull/3860) - Photoshop: synchronize image version with workfile [\#3854](https://github.com/pypeclub/OpenPype/pull/3854) +- General: Transcoding handle float2 attr type [\#3849](https://github.com/pypeclub/OpenPype/pull/3849) - General: Simple script for getting license information about used packages [\#3843](https://github.com/pypeclub/OpenPype/pull/3843) - Houdini: Increment current file on workfile publish [\#3840](https://github.com/pypeclub/OpenPype/pull/3840) - Publisher: Add new publisher to host tools [\#3833](https://github.com/pypeclub/OpenPype/pull/3833) @@ -20,9 +23,15 @@ **🐛 Bug fixes** +- Flame: loading multilayer exr to batch/reel is working [\#3901](https://github.com/pypeclub/OpenPype/pull/3901) +- Hiero: Fix inventory check on launch [\#3895](https://github.com/pypeclub/OpenPype/pull/3895) +- WebPublisher: Fix import after refactor [\#3891](https://github.com/pypeclub/OpenPype/pull/3891) +- TVPaint: Fix renaming of rendered files [\#3882](https://github.com/pypeclub/OpenPype/pull/3882) +- Publisher: Nice checkbox visible in Python 2 [\#3877](https://github.com/pypeclub/OpenPype/pull/3877) - Settings: Add missing default settings [\#3870](https://github.com/pypeclub/OpenPype/pull/3870) - General: Copy of workfile does not use 'copy' function but 'copyfile' [\#3869](https://github.com/pypeclub/OpenPype/pull/3869) - Tray Publisher: skip plugin if otioTimeline is missing [\#3856](https://github.com/pypeclub/OpenPype/pull/3856) +- Flame: retimed attributes are integrated with settings [\#3855](https://github.com/pypeclub/OpenPype/pull/3855) - Maya: Extract Playblast fix textures + labelize viewport show settings [\#3852](https://github.com/pypeclub/OpenPype/pull/3852) - Ftrack: Url validation does not require ftrackapp [\#3834](https://github.com/pypeclub/OpenPype/pull/3834) - Maya+Ftrack: Change typo in family name `mayaascii` -\> `mayaAscii` [\#3820](https://github.com/pypeclub/OpenPype/pull/3820) @@ -30,9 +39,12 @@ **🔀 Refactored code** +- Houdini: Use new Extractor location [\#3894](https://github.com/pypeclub/OpenPype/pull/3894) +- Harmony: Use new Extractor location [\#3893](https://github.com/pypeclub/OpenPype/pull/3893) - Hiero: Use new Extractor location [\#3851](https://github.com/pypeclub/OpenPype/pull/3851) - Maya: Remove old legacy \(ftrack\) plug-ins that are of no use anymore [\#3819](https://github.com/pypeclub/OpenPype/pull/3819) - Nuke: Use new Extractor location [\#3799](https://github.com/pypeclub/OpenPype/pull/3799) +- Maya: Use new Extractor location [\#3775](https://github.com/pypeclub/OpenPype/pull/3775) **Merged pull requests:** @@ -54,7 +66,6 @@ - General: Better pixmap scaling [\#3809](https://github.com/pypeclub/OpenPype/pull/3809) - Photoshop: attempt to speed up ExtractImage [\#3793](https://github.com/pypeclub/OpenPype/pull/3793) - SyncServer: Added cli commands for sync server [\#3765](https://github.com/pypeclub/OpenPype/pull/3765) -- Kitsu: Drop 'entities root' setting. [\#3739](https://github.com/pypeclub/OpenPype/pull/3739) **🐛 Bug fixes** @@ -74,7 +85,6 @@ - Photoshop: Use new Extractor location [\#3789](https://github.com/pypeclub/OpenPype/pull/3789) - Blender: Use new Extractor location [\#3787](https://github.com/pypeclub/OpenPype/pull/3787) - AfterEffects: Use new Extractor location [\#3784](https://github.com/pypeclub/OpenPype/pull/3784) -- Maya: Use new Extractor location [\#3775](https://github.com/pypeclub/OpenPype/pull/3775) - General: Remove unused teshost [\#3773](https://github.com/pypeclub/OpenPype/pull/3773) - General: Copied 'Extractor' plugin to publish pipeline [\#3771](https://github.com/pypeclub/OpenPype/pull/3771) - General: Move queries of asset and representation links [\#3770](https://github.com/pypeclub/OpenPype/pull/3770) @@ -83,10 +93,7 @@ - Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) - General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) -- Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) -- Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) -- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) -- Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) **Merged pull requests:** @@ -106,22 +113,12 @@ - Maya: Fix typo in getPanel argument `with\_focus` -\> `withFocus` [\#3753](https://github.com/pypeclub/OpenPype/pull/3753) - General: Smaller fixes of imports [\#3748](https://github.com/pypeclub/OpenPype/pull/3748) - General: Logger tweaks [\#3741](https://github.com/pypeclub/OpenPype/pull/3741) -- Nuke: missing job dependency if multiple bake streams [\#3737](https://github.com/pypeclub/OpenPype/pull/3737) **🔀 Refactored code** - General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) -- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) -- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) -- Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) -- General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) -- AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) -- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) -- AfterEffects: Define AfterEffects as module [\#3728](https://github.com/pypeclub/OpenPype/pull/3728) -- General: Replace PypeLogger with Logger [\#3725](https://github.com/pypeclub/OpenPype/pull/3725) -- Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) ## [3.14.0](https://github.com/pypeclub/OpenPype/tree/3.14.0) (2022-08-18) diff --git a/openpype/version.py b/openpype/version.py index 26b145f1db..fd6e894fe2 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.3-nightly.3" +__version__ = "3.14.3-nightly.4" From 2e3e799f34955a31a946b939411cdb477eb33da6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 24 Sep 2022 11:33:03 +0200 Subject: [PATCH 511/513] Fix PublishIconButton drawing disabled icon with the color specified - Previously the color to draw was ignored when button was disabled because default color was applied to the disabled state --- openpype/tools/publisher/widgets/widgets.py | 40 +++++++-------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index aa7e3be687..1b081cc4a1 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -125,28 +125,19 @@ class PublishIconBtn(IconButton): def __init__(self, pixmap_path, *args, **kwargs): super(PublishIconBtn, self).__init__(*args, **kwargs) - loaded_image = QtGui.QImage(pixmap_path) + icon = self.generate_icon(pixmap_path, + enabled_color=QtCore.Qt.white, + disabled_color=QtGui.QColor("#5b6779")) + self.setIcon(icon) - pixmap = self.paint_image_with_color(loaded_image, QtCore.Qt.white) - - self._base_image = loaded_image - self._enabled_icon = QtGui.QIcon(pixmap) - self._disabled_icon = None - - self.setIcon(self._enabled_icon) - - def get_enabled_icon(self): - """Enabled icon.""" - return self._enabled_icon - - def get_disabled_icon(self): - """Disabled icon.""" - if self._disabled_icon is None: - pixmap = self.paint_image_with_color( - self._base_image, QtCore.Qt.gray - ) - self._disabled_icon = QtGui.QIcon(pixmap) - return self._disabled_icon + def generate_icon(self, pixmap_path, enabled_color, disabled_color): + icon = QtGui.QIcon() + image = QtGui.QImage(pixmap_path) + enabled_pixmap = self.paint_image_with_color(image, enabled_color) + icon.addPixmap(enabled_pixmap, icon.Normal) + disabled_pixmap = self.paint_image_with_color(image, disabled_color) + icon.addPixmap(disabled_pixmap, icon.Disabled) + return icon @staticmethod def paint_image_with_color(image, color): @@ -187,13 +178,6 @@ class PublishIconBtn(IconButton): return pixmap - def setEnabled(self, enabled): - super(PublishIconBtn, self).setEnabled(enabled) - if self.isEnabled(): - self.setIcon(self.get_enabled_icon()) - else: - self.setIcon(self.get_disabled_icon()) - class ResetBtn(PublishIconBtn): """Publish reset button.""" From d6949754a1e6bff33d85df0a2012d15dbf825214 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 26 Sep 2022 11:07:17 +0200 Subject: [PATCH 512/513] Use colors from style/data.json --- openpype/tools/publisher/widgets/widgets.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 1b081cc4a1..d1fa71343c 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -16,6 +16,7 @@ from openpype.tools.utils import ( BaseClickableFrame, set_style_property, ) +from openpype.style import get_objected_colors from openpype.pipeline.create import ( SUBSET_NAME_ALLOWED_SYMBOLS, TaskNotSetError, @@ -125,9 +126,11 @@ class PublishIconBtn(IconButton): def __init__(self, pixmap_path, *args, **kwargs): super(PublishIconBtn, self).__init__(*args, **kwargs) - icon = self.generate_icon(pixmap_path, - enabled_color=QtCore.Qt.white, - disabled_color=QtGui.QColor("#5b6779")) + colors = get_objected_colors() + icon = self.generate_icon( + pixmap_path, + enabled_color=colors["font"].get_qcolor(), + disabled_color=colors["font-disabled"].get_qcolor()) self.setIcon(icon) def generate_icon(self, pixmap_path, enabled_color, disabled_color): From 6237c4ae8204f044da371594814f34eefd1e91f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 26 Sep 2022 11:07:34 +0200 Subject: [PATCH 513/513] Update "font-disabled" color --- openpype/style/data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index 15d9472e3e..adda49de23 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -20,7 +20,7 @@ "color": { "font": "#D3D8DE", "font-hover": "#F0F2F5", - "font-disabled": "#99A3B2", + "font-disabled": "#5b6779", "font-view-selection": "#ffffff", "font-view-hover": "#F0F2F5",