From e8d6f1d9e9764f90128938ce1386a5b767607c60 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 22:02:56 +0100 Subject: [PATCH 01/82] 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 02/82] 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 03/82] 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 04/82] 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 05/82] 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 06/82] 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 07/82] 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 08/82] 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 09/82] 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 10/82] 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 11/82] 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 12/82] 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 13/82] 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 14/82] 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 15/82] 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 16/82] 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 17/82] 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 5f0a8d700e5fc72570b70e56240b488819da3d43 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 11:22:10 +0200 Subject: [PATCH 18/82] 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 379a5f5d787f3d4fff3f16e3d6d4b1f6adea390e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 22:52:08 +0200 Subject: [PATCH 19/82] 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 20/82] 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 21/82] 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 22/82] 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 23/82] 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 229d31bc1ca10d51ab2b562ed128623a8895d26b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 23:41:42 +0200 Subject: [PATCH 24/82] 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 25/82] 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 26/82] 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 6186c63c599822bddaf4fc2c2a437831f00e62b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 14 Sep 2022 13:50:01 +0200 Subject: [PATCH 27/82] 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 28/82] 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 fa65e20ff7f4ff843ca54ac97897f33567c89eee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 12:09:59 +0200 Subject: [PATCH 29/82] 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 df370e5d3cd321b3a34a37b7247ccf356ae9e053 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 23:43:09 +0200 Subject: [PATCH 30/82] 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 31/82] 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 32/82] 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 33/82] 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 34/82] 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 35/82] 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 36/82] 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 37/82] 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 38/82] 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 508b17963d09bda307051d2483fd83753ca080b1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Sep 2022 15:41:16 +0200 Subject: [PATCH 39/82] 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 40/82] 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 41/82] 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 8dc0f6f011c17c455f62269768fe0c728f76631b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 19 Sep 2022 13:15:27 +0200 Subject: [PATCH 42/82] 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 43/82] 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 44/82] 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 45/82] 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 46/82] 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 47/82] 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 48/82] 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 49/82] 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 50/82] 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 51/82] 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 52/82] 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 53/82] 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 1f035a1eee956d56b55e86dd97f5090fdab81894 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Sep 2022 01:24:24 +0200 Subject: [PATCH 54/82] 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 55/82] 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 56/82] 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 57/82] 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 58/82] 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 59/82] 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 60/82] 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 61/82] 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 62/82] 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 63/82] 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 cefe853cc6c932771ffd9f2325c783942fbff3da Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 20 Sep 2022 11:29:23 +0200 Subject: [PATCH 64/82] 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 b0f7db52c84e40fa7fadfeb2fd180e2874fe950c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 20 Sep 2022 17:09:58 +0200 Subject: [PATCH 65/82] 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 66/82] 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 67/82] 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 68/82] 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 69/82] 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 70/82] 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 71/82] 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 72/82] 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 73/82] 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 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 74/82] 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 75/82] 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 76/82] 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 a317c8f5fd3766eddf11cc414643eff5fc975def Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 21 Sep 2022 15:21:10 +0200 Subject: [PATCH 77/82] 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 78/82] 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 c35dde88ee2ce3e5522010fd44838722d7bbd2c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 10:49:01 +0200 Subject: [PATCH 79/82] 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 c4127208d24d58b048773f16465d6ad208f3a35e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Sep 2022 11:29:01 +0200 Subject: [PATCH 80/82] 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 806865e7d7d45639cb5c7948a697490b21680d0a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 22 Sep 2022 11:42:15 +0200 Subject: [PATCH 81/82] 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 54d5724d6ac1ad7478e4df61de10e33833c1dba9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Sep 2022 11:59:06 +0200 Subject: [PATCH 82/82] 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