From ae7a6555139219c0bb384192965055a0467184de Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 Jun 2020 13:03:10 +0300 Subject: [PATCH 01/23] fix(resolve): transition to new structure --- pype/hooks/resolve/prelaunch.py | 6 +- pype/hosts/resolve/lib.py | 2 +- pype/hosts/resolve/pipeline.py | 14 ++-- pype/hosts/resolve/preload_console.py | 2 +- .../resolve/utility_scripts/Pype_menu.py | 2 +- .../utility_scripts/__dev_compound_clip.py | 65 ------------------- .../resolve/utility_scripts/__test_pyblish.py | 57 ---------------- .../utility_scripts/__test_subprocess.py | 35 ---------- 8 files changed, 13 insertions(+), 170 deletions(-) delete mode 100644 pype/hosts/resolve/utility_scripts/__dev_compound_clip.py delete mode 100644 pype/hosts/resolve/utility_scripts/__test_pyblish.py delete mode 100644 pype/hosts/resolve/utility_scripts/__test_subprocess.py diff --git a/pype/hooks/resolve/prelaunch.py b/pype/hooks/resolve/prelaunch.py index bddeccf4a39..a122b878686 100644 --- a/pype/hooks/resolve/prelaunch.py +++ b/pype/hooks/resolve/prelaunch.py @@ -46,13 +46,14 @@ def execute(self, *args, env: dict = None) -> bool: "`RESOLVE_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n" f"RESOLVE_UTILITY_SCRIPTS_DIR: `{us_dir}`" ) + self.log.debug(f"-- us_dir: `{us_dir}`") # correctly format path for pre python script pre_py_sc = os.path.normpath(env.get("PRE_PYTHON_SCRIPT", "")) env["PRE_PYTHON_SCRIPT"] = pre_py_sc - + self.log.debug(f"-- pre_py_sc: `{pre_py_sc}`...") try: - __import__("pype.resolve") + __import__("pype.hosts.resolve") __import__("pyblish") except ImportError as e: @@ -62,6 +63,7 @@ def execute(self, *args, env: dict = None) -> bool: else: # Resolve Setup integration importlib.reload(utils) + self.log.debug(f"-- utils.__file__: `{utils.__file__}`") utils.setup(env) return True diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 2576136df5a..2e759ab96ce 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,6 +1,6 @@ import sys from .utils import get_resolve_module -from pypeapp import Logger +from pype.api import Logger log = Logger().get_logger(__name__, "resolve") diff --git a/pype/hosts/resolve/pipeline.py b/pype/hosts/resolve/pipeline.py index 967aed14363..8dfb94486bc 100644 --- a/pype/hosts/resolve/pipeline.py +++ b/pype/hosts/resolve/pipeline.py @@ -6,23 +6,21 @@ from avalon.tools import workfiles from avalon import api as avalon from pyblish import api as pyblish -from pypeapp import Logger +import pype +from pype.api import Logger log = Logger().get_logger(__name__, "resolve") # self = sys.modules[__name__] AVALON_CONFIG = os.environ["AVALON_CONFIG"] -PARENT_DIR = os.path.dirname(__file__) -PACKAGE_DIR = os.path.dirname(PARENT_DIR) -PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") -LOAD_PATH = os.path.join(PLUGINS_DIR, "resolve", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "resolve", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "resolve", "inventory") +LOAD_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "load") +CREATE_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "create") +INVENTORY_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "inventory") PUBLISH_PATH = os.path.join( - PLUGINS_DIR, "resolve", "publish" + pype.PLUGINS_DIR, "resolve", "publish" ).replace("\\", "/") AVALON_CONTAINERS = ":AVALON_CONTAINERS" diff --git a/pype/hosts/resolve/preload_console.py b/pype/hosts/resolve/preload_console.py index ea1bd4f180b..58975777b83 100644 --- a/pype/hosts/resolve/preload_console.py +++ b/pype/hosts/resolve/preload_console.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import time from pype.hosts.resolve.utils import get_resolve_module -from pypeapp import Logger +from pype.api import Logger log = Logger().get_logger(__name__, "resolve") diff --git a/pype/hosts/resolve/utility_scripts/Pype_menu.py b/pype/hosts/resolve/utility_scripts/Pype_menu.py index 1f5cd362771..230a7a80f0d 100644 --- a/pype/hosts/resolve/utility_scripts/Pype_menu.py +++ b/pype/hosts/resolve/utility_scripts/Pype_menu.py @@ -3,7 +3,7 @@ import avalon.api as avalon import pype -from pypeapp import Logger +from pype.api import Logger log = Logger().get_logger(__name__) diff --git a/pype/hosts/resolve/utility_scripts/__dev_compound_clip.py b/pype/hosts/resolve/utility_scripts/__dev_compound_clip.py deleted file mode 100644 index fe47008c70a..00000000000 --- a/pype/hosts/resolve/utility_scripts/__dev_compound_clip.py +++ /dev/null @@ -1,65 +0,0 @@ -#! python3 -# -*- coding: utf-8 -*- - - -# convert clip def -def convert_clip(timeline=None): - """Convert timeline item (clip) into compound clip pype container - - Args: - timeline (MediaPool.Timeline): Object of timeline - - Returns: - bool: `True` if success - - Raises: - Exception: description - - """ - pass - - -# decorator function create_current_timeline_media_bin() -def create_current_timeline_media_bin(timeline=None): - """Convert timeline item (clip) into compound clip pype container - - Args: - timeline (MediaPool.Timeline): Object of timeline - - Returns: - bool: `True` if success - - Raises: - Exception: description - - """ - pass - - -# decorator function get_selected_track_items() -def get_selected_track_items(): - """Convert timeline item (clip) into compound clip pype container - - Args: - timeline (MediaPool.Timeline): Object of timeline - - Returns: - bool: `True` if success - - Raises: - Exception: description - - """ - print("testText") - - -# PypeCompoundClip() class -class PypeCompoundClip(object): - """docstring for .""" - - def __init__(self, arg): - super(self).__init__() - self.arg = arg - - def create_compound_clip(self): - pass diff --git a/pype/hosts/resolve/utility_scripts/__test_pyblish.py b/pype/hosts/resolve/utility_scripts/__test_pyblish.py deleted file mode 100644 index a6fe9910255..00000000000 --- a/pype/hosts/resolve/utility_scripts/__test_pyblish.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import sys -import pype -import importlib -import pyblish.api -import pyblish.util -import avalon.api -from avalon.tools import publish -from pypeapp import Logger - -log = Logger().get_logger(__name__) - - -def main(env): - # Registers pype's Global pyblish plugins - pype.install() - - # Register Host (and it's pyblish plugins) - host_name = env["AVALON_APP"] - # TODO not sure if use "pype." or "avalon." for host import - host_import_str = f"pype.{host_name}" - - try: - host_module = importlib.import_module(host_import_str) - except ModuleNotFoundError: - log.error(( - f"Host \"{host_name}\" can't be imported." - f" Import string \"{host_import_str}\" failed." - )) - return False - - avalon.api.install(host_module) - - # Register additional paths - addition_paths_str = env.get("PUBLISH_PATHS") or "" - addition_paths = addition_paths_str.split(os.pathsep) - for path in addition_paths: - path = os.path.normpath(path) - if not os.path.exists(path): - continue - - pyblish.api.register_plugin_path(path) - - # Register project specific plugins - project_name = os.environ["AVALON_PROJECT"] - project_plugins_paths = env.get("PYPE_PROJECT_PLUGINS") or "" - for path in project_plugins_paths.split(os.pathsep): - plugin_path = os.path.join(path, project_name, "plugins") - if os.path.exists(plugin_path): - pyblish.api.register_plugin_path(plugin_path) - - return publish.show() - - -if __name__ == "__main__": - result = main(os.environ) - sys.exit(not bool(result)) diff --git a/pype/hosts/resolve/utility_scripts/__test_subprocess.py b/pype/hosts/resolve/utility_scripts/__test_subprocess.py deleted file mode 100644 index bdc57bbf007..00000000000 --- a/pype/hosts/resolve/utility_scripts/__test_subprocess.py +++ /dev/null @@ -1,35 +0,0 @@ -#! python3 -# -*- coding: utf-8 -*- -import os -from pypeapp import execute, Logger -from pype.hosts.resolve.utils import get_resolve_module - -log = Logger().get_logger("Resolve") - -CURRENT_DIR = os.getenv("RESOLVE_UTILITY_SCRIPTS_DIR", "") -python_dir = os.getenv("PYTHON36_RESOLVE") -python_exe = os.path.normpath( - os.path.join(python_dir, "python.exe") -) - -resolve = get_resolve_module() -PM = resolve.GetProjectManager() -P = PM.GetCurrentProject() - -log.info(P.GetName()) - - -# ______________________________________________________ -# testing subprocessing Scripts -testing_py = os.path.join(CURRENT_DIR, "ResolvePageSwitcher.py") -testing_py = os.path.normpath(testing_py) -log.info(f"Testing path to script: `{testing_py}`") - -returncode = execute( - [python_exe, os.path.normpath(testing_py)], - env=dict(os.environ) -) - -# Check if output file exists -if returncode != 0: - log.error("Executing failed!") From a4d3b40136175ae299ae0306da15c62507e8d998 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 Jun 2020 13:03:38 +0300 Subject: [PATCH 02/23] feat(resolve): adding currentFile to collect project --- pype/plugins/resolve/publish/collect_host.py | 17 ----------- .../resolve/publish/collect_project.py | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 17 deletions(-) delete mode 100644 pype/plugins/resolve/publish/collect_host.py create mode 100644 pype/plugins/resolve/publish/collect_project.py diff --git a/pype/plugins/resolve/publish/collect_host.py b/pype/plugins/resolve/publish/collect_host.py deleted file mode 100644 index a5c4b0936c0..00000000000 --- a/pype/plugins/resolve/publish/collect_host.py +++ /dev/null @@ -1,17 +0,0 @@ -import pyblish.api -from pype.hosts.resolve.utils import get_resolve_module - - -class CollectProject(pyblish.api.ContextPlugin): - """Collect Project object""" - - order = pyblish.api.CollectorOrder - 0.1 - label = "Collect Project" - hosts = ["resolve"] - - def process(self, context): - resolve = get_resolve_module() - PM = resolve.GetProjectManager() - P = PM.GetCurrentProject() - - self.log.info(P.GetName()) diff --git a/pype/plugins/resolve/publish/collect_project.py b/pype/plugins/resolve/publish/collect_project.py new file mode 100644 index 00000000000..aa57f936199 --- /dev/null +++ b/pype/plugins/resolve/publish/collect_project.py @@ -0,0 +1,29 @@ +import os +import pyblish.api +from pype.hosts.resolve.utils import get_resolve_module + + +class CollectProject(pyblish.api.ContextPlugin): + """Collect Project object""" + + order = pyblish.api.CollectorOrder - 0.1 + label = "Collect Project" + hosts = ["resolve"] + + def process(self, context): + exported_projet_ext = ".drp" + current_dir = os.getenv("AVALON_WORKDIR") + resolve = get_resolve_module() + PM = resolve.GetProjectManager() + P = PM.GetCurrentProject() + name = P.GetName() + + fname = name + exported_projet_ext + current_file = os.path.join(current_dir, fname) + normalised = os.path.normpath(current_file) + + context.data["project"] = P + context.data["currentFile"] = normalised + + self.log.info(name) + self.log.debug(normalised) From 700c62617814d3e9e1c1026bf3423d7cf620eee3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 Jun 2020 17:13:17 +0300 Subject: [PATCH 03/23] feat(resolve): adding create shot clip --- pype/plugins/resolve/create/create_shot_clip.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pype/plugins/resolve/create/create_shot_clip.py diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py new file mode 100644 index 00000000000..97d56639229 --- /dev/null +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -0,0 +1,15 @@ +import avalon.api +from pype.hosts import resolve + + +class CreateShotClip(avalon.api.Creator): + """Publishable clip""" + + label = "Shot" + family = "clip" + icon = "film" + defaults = ["Main"] + + def process(self): + project = resolve.get_current_project() + self.log.info(f"Project name: {project.GetName()}") From 37fff09ecdafb1d4eb3ea8af070d387415c41ec9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 Jun 2020 18:27:51 +0300 Subject: [PATCH 04/23] feat(resolve): wip create plugins --- pype/hosts/resolve/__init__.py | 38 ++++++++++---- pype/hosts/resolve/action.py | 6 +-- pype/hosts/resolve/lib.py | 19 +++++-- pype/hosts/resolve/pipeline.py | 51 +++++++++++++++++-- pype/hosts/resolve/plugin.py | 21 +++++++- pype/hosts/resolve/utility_scripts/test.py | 19 +++++++ pype/hosts/resolve/utils.py | 18 +++---- pype/hosts/resolve/workio.py | 5 +- .../resolve/create/create_shot_clip.py | 10 ++-- 9 files changed, 150 insertions(+), 37 deletions(-) create mode 100644 pype/hosts/resolve/utility_scripts/test.py diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 72d6314b5e8..b7e6c7dee3c 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -1,17 +1,29 @@ +from .utils import ( + setup, + get_resolve_module +) + from .pipeline import ( install, uninstall, ls, containerise, publish, - launch_workfiles_app + launch_workfiles_app, + maintained_selection ) -from .utils import ( - setup, - get_resolve_module +from .lib import ( + get_project_manager, + get_current_project, + get_current_sequence, + set_project_manager_to_folder_name ) +from .menu import launch_pype_menu + +from .plugin import Creator + from .workio import ( open_file, save_file, @@ -21,12 +33,7 @@ work_root ) -from .lib import ( - get_project_manager, - set_project_manager_to_folder_name -) - -from .menu import launch_pype_menu +bmd = None __all__ = [ # pipeline @@ -37,6 +44,7 @@ "reload_pipeline", "publish", "launch_workfiles_app", + "maintained_selection", # utils "setup", @@ -44,16 +52,24 @@ # lib "get_project_manager", + "get_current_project", + "get_current_sequence", "set_project_manager_to_folder_name", # menu "launch_pype_menu", + # plugin + "Creator", + # workio "open_file", "save_file", "current_file", "has_unsaved_changes", "file_extensions", - "work_root" + "work_root", + + # singleton with black magic resolve module + "bmd" ] diff --git a/pype/hosts/resolve/action.py b/pype/hosts/resolve/action.py index 31830937c13..a9803cef4e4 100644 --- a/pype/hosts/resolve/action.py +++ b/pype/hosts/resolve/action.py @@ -21,9 +21,9 @@ class SelectInvalidAction(pyblish.api.Action): def process(self, context, plugin): try: - from pype.hosts.resolve.utils import get_resolve_module - resolve = get_resolve_module() - self.log.debug(resolve) + from . import get_project_manager + pm = get_project_manager() + self.log.debug(pm) except ImportError: raise ImportError("Current host is not Resolve") diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 2e759ab96ce..25e177eb1cd 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,5 +1,4 @@ import sys -from .utils import get_resolve_module from pype.api import Logger log = Logger().get_logger(__name__, "resolve") @@ -9,12 +8,26 @@ def get_project_manager(): + from . import bmd if not self.pm: - resolve = get_resolve_module() - self.pm = resolve.GetProjectManager() + self.pm = bmd.GetProjectManager() return self.pm +def get_current_project(): + # initialize project manager + get_project_manager() + + return self.pm.GetCurrentProject() + + +def get_current_sequence(): + # get current project + project = get_current_project() + + return project.GetCurrentTimeline() + + def set_project_manager_to_folder_name(folder_name): """ Sets context of Project manager to given folder by name. diff --git a/pype/hosts/resolve/pipeline.py b/pype/hosts/resolve/pipeline.py index 8dfb94486bc..91d06da2748 100644 --- a/pype/hosts/resolve/pipeline.py +++ b/pype/hosts/resolve/pipeline.py @@ -2,7 +2,7 @@ Basic avalon integration """ import os -# import sys +import contextlib from avalon.tools import workfiles from avalon import api as avalon from pyblish import api as pyblish @@ -11,8 +11,6 @@ log = Logger().get_logger(__name__, "resolve") -# self = sys.modules[__name__] - AVALON_CONFIG = os.environ["AVALON_CONFIG"] LOAD_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "load") @@ -38,11 +36,13 @@ def install(): See the Maya equivalent for inspiration on how to implement this. """ + from . import get_resolve_module # Disable all families except for the ones we explicitly want to see family_states = [ "imagesequence", - "mov" + "mov", + "clip" ] avalon.data["familiesStateDefault"] = False avalon.data["familiesStateToggled"] = family_states @@ -57,6 +57,8 @@ def install(): avalon.register_plugin_path(avalon.Creator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + get_resolve_module() + def uninstall(): """Uninstall all tha was installed @@ -138,3 +140,44 @@ def publish(parent): """Shorthand to publish from within host""" from avalon.tools import publish return publish.show(parent) + + +@contextlib.contextmanager +def maintained_selection(): + """Maintain selection during context + + Example: + >>> with maintained_selection(): + ... node['selected'].setValue(True) + >>> print(node['selected'].value()) + False + """ + from . import get_current_project + project = get_current_project() + nodes = [] + previous_selection = None + + # deselect all nodes + reset_selection() + + try: + # do the operation + yield + finally: + # unselect all selection in case there is some + reset_selection() + # and select all previously selected nodes + if previous_selection: + try: + for n in nodes: + if n not in previous_selection: + continue + n['selected'].setValue(True) + except ValueError as e: + log.warning(e) + + +def reset_selection(): + """Deselect all selected nodes + """ + pass diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 628d4bdb26e..513b9984f4f 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,6 +1,7 @@ from avalon import api -# from pype.hosts.resolve import lib as drlib +from pype.hosts import resolve from avalon.vendor import qargparse +from pype.api import config def get_reference_node_parents(ref): @@ -73,3 +74,21 @@ def remove(self, container): """Remove an existing `container` """ pass + + +class Creator(api.Creator): + """Creator class wrapper + """ + marker_color = "Purple" + + def __init__(self, *args, **kwargs): + super(Creator, self).__init__(*args, **kwargs) + self.presets = config.get_presets()['plugins']["resolve"][ + "create"].get(self.__class__.__name__, {}) + + # adding basic current context resolve objects + self.project = resolve.get_current_project() + self.sequence = resolve.get_current_sequence() + + # TODO: make sure no duplicity of subsets are in workfile + return diff --git a/pype/hosts/resolve/utility_scripts/test.py b/pype/hosts/resolve/utility_scripts/test.py new file mode 100644 index 00000000000..4c43507e621 --- /dev/null +++ b/pype/hosts/resolve/utility_scripts/test.py @@ -0,0 +1,19 @@ +#! python3 +import sys +from pype.api import Logger + +log = Logger().get_logger(__name__) + + +def main(): + import pype.hosts.resolve as bmdvr + bm = bmdvr.utils.get_resolve_module() + log.info(f"blackmagicmodule: {bm}") + +import DaVinciResolveScript as bmd +print(f"_>> bmd.scriptapp(Resolve): {bmd.scriptapp('Resolve')}") + + +if __name__ == "__main__": + result = main() + sys.exit(not bool(result)) diff --git a/pype/hosts/resolve/utils.py b/pype/hosts/resolve/utils.py index f5add53a6b2..74ce2dc98f6 100644 --- a/pype/hosts/resolve/utils.py +++ b/pype/hosts/resolve/utils.py @@ -9,18 +9,16 @@ import shutil from pypeapp import Logger - log = Logger().get_logger(__name__, "resolve") -self = sys.modules[__name__] -self.bmd = None - def get_resolve_module(): + from pype.hosts import resolve # dont run if already loaded - if self.bmd: - return self.bmd - + if resolve.bmd: + log.info(("resolve module is assigned to " + f"`pype.hosts.resolve.bmd`: {resolve.bmd}")) + return resolve.bmd try: """ The PYTHONPATH needs to be set correctly for this import @@ -71,8 +69,10 @@ def get_resolve_module(): ) sys.exit() # assign global var and return - self.bmd = bmd.scriptapp("Resolve") - return self.bmd + bmd = bmd.scriptapp("Resolve") + resolve.bmd = bmd + log.info(("Assigning resolve module to " + f"`pype.hosts.resolve.bmd`: {resolve.bmd}")) def _sync_utility_scripts(env=None): diff --git a/pype/hosts/resolve/workio.py b/pype/hosts/resolve/workio.py index e1e30a8734f..9d8d320a3c6 100644 --- a/pype/hosts/resolve/workio.py +++ b/pype/hosts/resolve/workio.py @@ -2,8 +2,9 @@ import os from pypeapp import Logger -from .lib import ( +from . import ( get_project_manager, + get_current_project, set_project_manager_to_folder_name ) @@ -26,7 +27,7 @@ def save_file(filepath): pm = get_project_manager() file = os.path.basename(filepath) fname, _ = os.path.splitext(file) - project = pm.GetCurrentProject() + project = get_current_project() name = project.GetName() if "Untitled Project" not in name: diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index 97d56639229..43a8ab0cbdb 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,8 +1,7 @@ -import avalon.api from pype.hosts import resolve -class CreateShotClip(avalon.api.Creator): +class CreateShotClip(resolve.Creator): """Publishable clip""" label = "Shot" @@ -10,6 +9,9 @@ class CreateShotClip(avalon.api.Creator): icon = "film" defaults = ["Main"] + presets = None + def process(self): - project = resolve.get_current_project() - self.log.info(f"Project name: {project.GetName()}") + print(f"Project name: {self.project.GetName()}") + print(f"Sequence name: {self.sequence.GetName()}") + print(self.presets) From 48c163a3319429ba63eb068c928fc77509c4f88c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 22 Jun 2020 19:30:17 +0300 Subject: [PATCH 05/23] feat(resolve): updating resolve integration wip --- pype/hosts/resolve/__init__.py | 12 +- pype/hosts/resolve/lib.py | 205 +++++++++++++++++- pype/hosts/resolve/pipeline.py | 20 +- pype/hosts/resolve/plugin.py | 8 +- pype/hosts/resolve/utility_scripts/test.py | 4 +- pype/hosts/resolve/utils.py | 16 +- .../resolve/create/create_shot_clip.py | 27 ++- 7 files changed, 255 insertions(+), 37 deletions(-) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index b7e6c7dee3c..5f651f4b298 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -17,6 +17,9 @@ get_project_manager, get_current_project, get_current_sequence, + get_current_track_items, + create_current_sequence_media_bin, + create_compound_clip, set_project_manager_to_folder_name ) @@ -33,7 +36,8 @@ work_root ) -bmd = None +bmdvr = None +bmdvf = None __all__ = [ # pipeline @@ -54,6 +58,9 @@ "get_project_manager", "get_current_project", "get_current_sequence", + "get_current_track_items", + "create_current_sequence_media_bin", + "create_compound_clip", "set_project_manager_to_folder_name", # menu @@ -71,5 +78,6 @@ "work_root", # singleton with black magic resolve module - "bmd" + "bmdvr", + "bmdvf" ] diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 25e177eb1cd..9232f8ec68c 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -8,9 +8,9 @@ def get_project_manager(): - from . import bmd + from . import bmdvr if not self.pm: - self.pm = bmd.GetProjectManager() + self.pm = bmdvr.GetProjectManager() return self.pm @@ -28,6 +28,207 @@ def get_current_sequence(): return project.GetCurrentTimeline() +def get_current_track_items( + filter=False, + track_type=None, + selecting_color=None): + """ Gets all available current timeline track items + """ + from pprint import pformat + track_type = track_type or "video" + selecting_color = selecting_color or "Chocolate" + project = get_current_project() + sequence = get_current_sequence() + selected_clips = list() + + # get all tracks count filtered by track type + sequence_video_count = sequence.GetTrackCount(track_type) + + # loop all tracks and get items + _clips = dict() + for track_index in range(1, (int(sequence_video_count) + 1)): + track_name = sequence.GetTrackName(track_type, track_index) + track_track_items = sequence.GetItemListInTrack( + track_type, track_index) + _clips[track_index] = track_track_items + + _data = { + "project": project, + "sequence": sequence, + "track": { + "name": track_name, + "index": track_index, + "type": track_type} + } + # get track item object and its color + for clip_index, ti in enumerate(_clips[track_index]): + data = _data.copy() + data["clip"] = { + "item": ti, + "index": clip_index + } + ti_color = ti.GetClipColor() + if filter is True: + if selecting_color in ti_color: + selected_clips.append(data) + ti.ClearClipColor() + else: + selected_clips.append(data) + + return selected_clips + + +def create_current_sequence_media_bin(sequence): + seq_name = sequence.GetName() + media_pool = get_current_project().GetMediaPool() + root_folder = media_pool.GetRootFolder() + sub_folders = root_folder.GetSubFolderList() + testing_names = list() + + print(f"_ sub_folders: {sub_folders}") + for subfolder in sub_folders: + subf_name = subfolder.GetName() + if seq_name in subf_name: + testing_names.append(subfolder) + else: + testing_names.append(False) + + matching = next((f for f in testing_names if f is not False), None) + + if not matching: + new_folder = media_pool.AddSubFolder(root_folder, seq_name) + media_pool.SetCurrentFolder(new_folder) + else: + media_pool.SetCurrentFolder(matching) + + return media_pool.GetCurrentFolder() + + +def create_compound_clip(clip_data, folder, presets): + """ + Convert timeline object into nested timeline object + + Args: + clip_data (dict): timeline item object packed into dict + with project, timeline (sequence) + folder (resolve.MediaPool.Folder): media pool folder object, + presets (dict): pype config plugin presets + + Returns: + resolve.MediaPoolItem: media pool item with compound clip timeline(cct) + """ + from pprint import pformat + + # get basic objects form data + project = clip_data["project"] + sequence = clip_data["sequence"] + clip = clip_data["clip"] + + # get details of objects + clip_item = clip["item"] + track = clip_data["track"] + + # build name + clip_name_split = clip_item.GetName().split(".") + name = "_".join([ + track["name"], + str(track["index"]), + clip_name_split[0], + str(clip["index"])] + ) + + # get metadata + mp_item = clip_item.GetMediaPoolItem() + mp_props = mp_item.GetClipProperty() + metadata = get_metadata_from_clip(clip_item) + mp = project.GetMediaPool() + + # keep original sequence + sq_origin = sequence + + # print(f"_ sequence: {sequence}") + # print(f"_ metadata: {pformat(metadata)}") + + # Set current folder to input media_pool_folder: + mp.SetCurrentFolder(folder) + + # check if clip doesnt exist already: + clips = folder.GetClipList() + cct = next((c for c in clips + if c.GetName() in name), None) + + if cct: + print(f"_ cct exists: {cct}") + return cct + + # Create empty timeline in current folder and give name: + cct = mp.CreateEmptyTimeline(name) + print(f"_ cct: {cct}") + + # Set current timeline to created timeline: + project.SetCurrentTimeline(cct) + + # Add input clip to the current timeline: + # TODO: set offsets if handles + done = mp.AppendToTimeline([{ + "mediaPoolItem": mp_item, + "startFrame": int(mp_props["Start"]), + "endFrame": int(mp_props["End"]) + }]) + print(f"_ done1: {done}") + + # Set current timeline to the working timeline: + project.SetCurrentTimeline(sq_origin) + + # Add collected metadata to the comound clip: + done = mp_item.SetClipProperty("pypeMetadata", metadata) + print(f"_ done2: {done}") + + return cct + + +def validate_tc(x): + # Validate and reformat timecode string + + if len(x) != 11: + print('Invalid timecode. Try again.') + + c = ':' + colonized = x[:2] + c + x[3:5] + c + x[6:8] + c + x[9:] + + if colonized.replace(':', '').isdigit(): + print(f"_ colonized: {colonized}") + return colonized + else: + print('Invalid timecode. Try again.') + + +def get_metadata_from_clip(clip): + """ + Collect all metadata from resolve timeline item + + Args: + clip (resolve.TimelineItem): timeline item object + + Returns: + dict: all collected metadata as key: values + """ + mp_item = clip.GetMediaPoolItem() + + data = { + "clipIn": clip.GetStart(), + "clipOut": clip.GetEnd(), + "clipLeftOffset": clip.GetLeftOffset(), + "clipRightOffset": clip.GetRightOffset(), + "clipMarkers": clip.GetMarkers(), + "clipFlags": clip.GetFlagList(), + "sourceMetadata": mp_item.GetMetadata(), + "sourceId": mp_item.GetMediaId(), + "sourceProperties": mp_item.GetClipProperty() + } + return data + + def set_project_manager_to_folder_name(folder_name): """ Sets context of Project manager to given folder by name. diff --git a/pype/hosts/resolve/pipeline.py b/pype/hosts/resolve/pipeline.py index 91d06da2748..92bef2e13b7 100644 --- a/pype/hosts/resolve/pipeline.py +++ b/pype/hosts/resolve/pipeline.py @@ -152,29 +152,11 @@ def maintained_selection(): >>> print(node['selected'].value()) False """ - from . import get_current_project - project = get_current_project() - nodes = [] - previous_selection = None - - # deselect all nodes - reset_selection() - try: # do the operation yield finally: - # unselect all selection in case there is some - reset_selection() - # and select all previously selected nodes - if previous_selection: - try: - for n in nodes: - if n not in previous_selection: - continue - n['selected'].setValue(True) - except ValueError as e: - log.warning(e) + pass def reset_selection(): diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 513b9984f4f..002d12106d6 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -89,6 +89,10 @@ def __init__(self, *args, **kwargs): # adding basic current context resolve objects self.project = resolve.get_current_project() self.sequence = resolve.get_current_sequence() - - # TODO: make sure no duplicity of subsets are in workfile + + if (self.options or {}).get("useSelection"): + self.selected = resolve.get_current_track_items(filter=True) + else: + self.selected = resolve.get_current_track_items(filter=False) + return diff --git a/pype/hosts/resolve/utility_scripts/test.py b/pype/hosts/resolve/utility_scripts/test.py index 4c43507e621..cf7db3b7e5b 100644 --- a/pype/hosts/resolve/utility_scripts/test.py +++ b/pype/hosts/resolve/utility_scripts/test.py @@ -10,8 +10,8 @@ def main(): bm = bmdvr.utils.get_resolve_module() log.info(f"blackmagicmodule: {bm}") -import DaVinciResolveScript as bmd -print(f"_>> bmd.scriptapp(Resolve): {bmd.scriptapp('Resolve')}") +import DaVinciResolveScript as bmdvr +print(f"_>> bmdvr.scriptapp(Resolve): {bmdvr.scriptapp('Resolve')}") if __name__ == "__main__": diff --git a/pype/hosts/resolve/utils.py b/pype/hosts/resolve/utils.py index 74ce2dc98f6..dcc92c5b8de 100644 --- a/pype/hosts/resolve/utils.py +++ b/pype/hosts/resolve/utils.py @@ -15,10 +15,10 @@ def get_resolve_module(): from pype.hosts import resolve # dont run if already loaded - if resolve.bmd: + if resolve.bmdvr: log.info(("resolve module is assigned to " - f"`pype.hosts.resolve.bmd`: {resolve.bmd}")) - return resolve.bmd + f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}")) + return resolve.bmdvr try: """ The PYTHONPATH needs to be set correctly for this import @@ -69,10 +69,14 @@ def get_resolve_module(): ) sys.exit() # assign global var and return - bmd = bmd.scriptapp("Resolve") - resolve.bmd = bmd + bmdvr = bmd.scriptapp("Resolve") + bmdvf = bmd.scriptapp("Fusion") + resolve.bmdvr = bmdvr + resolve.bmdvf = bmdvf log.info(("Assigning resolve module to " - f"`pype.hosts.resolve.bmd`: {resolve.bmd}")) + f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}")) + log.info(("Assigning resolve module to " + f"`pype.hosts.resolve.bmdvf`: {resolve.bmdvf}")) def _sync_utility_scripts(env=None): diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index 43a8ab0cbdb..c48ca3a5a64 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,6 +1,6 @@ +from pprint import pformat from pype.hosts import resolve - class CreateShotClip(resolve.Creator): """Publishable clip""" @@ -12,6 +12,25 @@ class CreateShotClip(resolve.Creator): presets = None def process(self): - print(f"Project name: {self.project.GetName()}") - print(f"Sequence name: {self.sequence.GetName()}") - print(self.presets) + project = self.project + sequence = self.sequence + presets = self.presets + print(f"__ selected_clips: {self.selected}") + + # sequence attrs + sq_frame_start = self.sequence.GetStartFrame() + sq_markers = self.sequence.GetMarkers() + print(f"__ sq_frame_start: {pformat(sq_frame_start)}") + print(f"__ seq_markers: {pformat(sq_markers)}") + + # create media bin for compound clips (trackItems) + mp_folder = resolve.create_current_sequence_media_bin(self.sequence) + print(f"_ mp_folder: {mp_folder.GetName()}") + + for t_data in self.selected: + print(t_data) + # convert track item to timeline media pool item + c_clip = resolve.create_compound_clip( + t_data, mp_folder, presets) + + # replace orig clip with compound_clip From 8e91764c4c06aff57d158e853e3e51d191dcd3dd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Jun 2020 12:47:06 +0300 Subject: [PATCH 06/23] feat(resolve): create compound clip wip --- pype/hosts/resolve/lib.py | 34 ++++++++++++++++++++++++---------- pype/hosts/resolve/utils.py | 4 ++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 9232f8ec68c..5a5980a4623 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,4 +1,5 @@ import sys +import json from pype.api import Logger log = Logger().get_logger(__name__, "resolve") @@ -140,14 +141,14 @@ def create_compound_clip(clip_data, folder, presets): # get metadata mp_item = clip_item.GetMediaPoolItem() mp_props = mp_item.GetClipProperty() - metadata = get_metadata_from_clip(clip_item) + clip_attributes = get_clip_attributes(clip_item) mp = project.GetMediaPool() # keep original sequence sq_origin = sequence - # print(f"_ sequence: {sequence}") - # print(f"_ metadata: {pformat(metadata)}") + print(f"_ sequence: {sequence}") + print(f"_ metadata: {pformat(clip_attributes)}") # Set current folder to input media_pool_folder: mp.SetCurrentFolder(folder) @@ -163,7 +164,12 @@ def create_compound_clip(clip_data, folder, presets): # Create empty timeline in current folder and give name: cct = mp.CreateEmptyTimeline(name) - print(f"_ cct: {cct}") + + # check if clip doesnt exist already: + clips = folder.GetClipList() + cct = next((c for c in clips + if c.GetName() in name), None) + print(f"_ cct created: {cct}") # Set current timeline to created timeline: project.SetCurrentTimeline(cct) @@ -180,10 +186,19 @@ def create_compound_clip(clip_data, folder, presets): # Set current timeline to the working timeline: project.SetCurrentTimeline(sq_origin) - # Add collected metadata to the comound clip: - done = mp_item.SetClipProperty("pypeMetadata", metadata) + # Add collected metadata and attributes to the comound clip: + clip_attributes["VFX Notes"] = mp_item.GetMetadata( + "VFX Notes")["VFX Notes"] + clip_attributes = json.dumps(clip_attributes) + + for k, v in mp_item.GetMetadata().items(): + done = cct.SetMetadata(k, v) + + done = cct.SetMetadata("VFX Notes", clip_attributes) print(f"_ done2: {done}") + # # add clip item as take to timeline + # AddTake(cct, startFrame, endFrame) return cct @@ -203,15 +218,15 @@ def validate_tc(x): print('Invalid timecode. Try again.') -def get_metadata_from_clip(clip): +def get_clip_attributes(clip): """ - Collect all metadata from resolve timeline item + Collect basic atrributes from resolve timeline item Args: clip (resolve.TimelineItem): timeline item object Returns: - dict: all collected metadata as key: values + dict: all collected attributres as key: values """ mp_item = clip.GetMediaPoolItem() @@ -222,7 +237,6 @@ def get_metadata_from_clip(clip): "clipRightOffset": clip.GetRightOffset(), "clipMarkers": clip.GetMarkers(), "clipFlags": clip.GetFlagList(), - "sourceMetadata": mp_item.GetMetadata(), "sourceId": mp_item.GetMediaId(), "sourceProperties": mp_item.GetClipProperty() } diff --git a/pype/hosts/resolve/utils.py b/pype/hosts/resolve/utils.py index dcc92c5b8de..e11cc64b3ba 100644 --- a/pype/hosts/resolve/utils.py +++ b/pype/hosts/resolve/utils.py @@ -70,9 +70,9 @@ def get_resolve_module(): sys.exit() # assign global var and return bmdvr = bmd.scriptapp("Resolve") - bmdvf = bmd.scriptapp("Fusion") + # bmdvf = bmd.scriptapp("Fusion") resolve.bmdvr = bmdvr - resolve.bmdvf = bmdvf + resolve.bmdvf = bmdvr.Fusion() log.info(("Assigning resolve module to " f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}")) log.info(("Assigning resolve module to " From 34c27c3fbf7aac183017540f73e15e8335416c2d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Jun 2020 21:02:41 +0300 Subject: [PATCH 07/23] feat(resolve): compound clip create with sequencial rename --- pype/hosts/resolve/lib.py | 228 ++++++++++++++---- .../resolve/create/create_shot_clip.py | 16 +- 2 files changed, 192 insertions(+), 52 deletions(-) diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 5a5980a4623..50b70241c0c 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,11 +1,15 @@ import sys import json +from opentimelineio import opentime + from pype.api import Logger log = Logger().get_logger(__name__, "resolve") self = sys.modules[__name__] self.pm = None +self.rename_index = 0 +self.rename_add = 0 def get_project_manager(): @@ -35,7 +39,6 @@ def get_current_track_items( selecting_color=None): """ Gets all available current timeline track items """ - from pprint import pformat track_type = track_type or "video" selecting_color = selecting_color or "Chocolate" project = get_current_project() @@ -105,7 +108,75 @@ def create_current_sequence_media_bin(sequence): return media_pool.GetCurrentFolder() -def create_compound_clip(clip_data, folder, presets): +def get_name_with_data(clip_data, presets): + """ + Take hierarchy data from presets and build name with parents data + + Args: + clip_data (dict): clip data from `get_current_track_items()` + presets (dict): data from create plugin + + Returns: + list: name, data + + """ + def _replace_hash_to_expression(name, text): + _spl = text.split("#") + _len = (len(_spl) - 1) + _repl = f"{{{name}:0>{_len}}}" + new_text = text.replace(("#" * _len), _repl) + return new_text + + # presets data + clip_name = presets["clipName"] + hierarchy = presets["hierarchy"] + hierarchy_data = presets["hierarchyData"].copy() + count_from = presets["countFrom"] + steps = presets["steps"] + + # reset rename_add + if self.rename_add < count_from: + self.rename_add = count_from + + # shot num calculate + if self.rename_index == 0: + shot_num = self.rename_add + else: + shot_num = self.rename_add + steps + + print(f"shot_num: {shot_num}") + + # clip data + _data = { + "sequence": clip_data["sequence"].GetName(), + "track": clip_data["track"]["name"].replace(" ", "_"), + "shot": shot_num + } + + # solve # in test to pythonic explression + for k, v in hierarchy_data.items(): + if "#" not in v: + continue + hierarchy_data[k] = _replace_hash_to_expression(k, v) + + # fill up pythonic expresisons + for k, v in hierarchy_data.items(): + hierarchy_data[k] = v.format(**_data) + + # fill up clip name and hierarchy keys + hierarchy = hierarchy.format(**hierarchy_data) + clip_name = clip_name.format(**hierarchy_data) + + self.rename_add = shot_num + print(f"shot_num: {shot_num}") + + return (clip_name, { + "hierarchy": hierarchy, + "hierarchyData": hierarchy_data + }) + + +def create_compound_clip(clip_data, folder, rename=False, **kwargs): """ Convert timeline object into nested timeline object @@ -113,12 +184,12 @@ def create_compound_clip(clip_data, folder, presets): clip_data (dict): timeline item object packed into dict with project, timeline (sequence) folder (resolve.MediaPool.Folder): media pool folder object, - presets (dict): pype config plugin presets + rename (bool)[optional]: renaming in sequence or not + kwargs (optional): additional data needed for rename=True (presets) Returns: resolve.MediaPoolItem: media pool item with compound clip timeline(cct) """ - from pprint import pformat # get basic objects form data project = clip_data["project"] @@ -129,27 +200,53 @@ def create_compound_clip(clip_data, folder, presets): clip_item = clip["item"] track = clip_data["track"] - # build name - clip_name_split = clip_item.GetName().split(".") - name = "_".join([ - track["name"], - str(track["index"]), - clip_name_split[0], - str(clip["index"])] - ) + mp = project.GetMediaPool() + + # get clip attributes + clip_attributes = get_clip_attributes(clip_item) + + if rename: + presets = kwargs.get("presets") + if presets: + name, data = get_name_with_data(clip_data, presets) + # add hirarchy data to clip attributes + clip_attributes.update(data) + else: + name = "{:0>3}_{:0>4}".format( + int(track["index"]), int(clip["index"])) + else: + # build name + clip_name_split = clip_item.GetName().split(".") + name = "_".join([ + track["name"], + str(track["index"]), + clip_name_split[0], + str(clip["index"])] + ) # get metadata mp_item = clip_item.GetMediaPoolItem() mp_props = mp_item.GetClipProperty() - clip_attributes = get_clip_attributes(clip_item) - mp = project.GetMediaPool() + + mp_first_frame = int(mp_props["Start"]) + mp_last_frame = int(mp_props["End"]) + + # initialize basic source timing for otio + ci_l_offset = clip_item.GetLeftOffset() + ci_duration = clip_item.GetDuration() + rate = float(mp_props["FPS"]) + + # source rational times + mp_in_rc = opentime.RationalTime((ci_l_offset), rate) + mp_out_rc = opentime.RationalTime((ci_l_offset + ci_duration - 1), rate) + + # get frame in and out for clip swaping + in_frame = opentime.to_frames(mp_in_rc) + out_frame = opentime.to_frames(mp_out_rc) # keep original sequence sq_origin = sequence - print(f"_ sequence: {sequence}") - print(f"_ metadata: {pformat(clip_attributes)}") - # Set current folder to input media_pool_folder: mp.SetCurrentFolder(folder) @@ -160,48 +257,89 @@ def create_compound_clip(clip_data, folder, presets): if cct: print(f"_ cct exists: {cct}") - return cct - - # Create empty timeline in current folder and give name: - cct = mp.CreateEmptyTimeline(name) + else: + # Create empty timeline in current folder and give name: + cct = mp.CreateEmptyTimeline(name) - # check if clip doesnt exist already: - clips = folder.GetClipList() - cct = next((c for c in clips - if c.GetName() in name), None) - print(f"_ cct created: {cct}") + # check if clip doesnt exist already: + clips = folder.GetClipList() + cct = next((c for c in clips + if c.GetName() in name), None) + print(f"_ cct created: {cct}") - # Set current timeline to created timeline: - project.SetCurrentTimeline(cct) + # Set current timeline to created timeline: + project.SetCurrentTimeline(cct) - # Add input clip to the current timeline: - # TODO: set offsets if handles - done = mp.AppendToTimeline([{ - "mediaPoolItem": mp_item, - "startFrame": int(mp_props["Start"]), - "endFrame": int(mp_props["End"]) - }]) - print(f"_ done1: {done}") + # Add input clip to the current timeline: + mp.AppendToTimeline([{ + "mediaPoolItem": mp_item, + "startFrame": mp_first_frame, + "endFrame": mp_last_frame + }]) - # Set current timeline to the working timeline: - project.SetCurrentTimeline(sq_origin) + # Set current timeline to the working timeline: + project.SetCurrentTimeline(sq_origin) # Add collected metadata and attributes to the comound clip: - clip_attributes["VFX Notes"] = mp_item.GetMetadata( - "VFX Notes")["VFX Notes"] + if clip_attributes.get("VFX Notes"): + clip_attributes["VFX Notes"] = mp_item.GetMetadata( + "VFX Notes")["VFX Notes"] clip_attributes = json.dumps(clip_attributes) + # add attributes to metadata for k, v in mp_item.GetMetadata().items(): - done = cct.SetMetadata(k, v) + cct.SetMetadata(k, v) - done = cct.SetMetadata("VFX Notes", clip_attributes) - print(f"_ done2: {done}") + # add metadata to cct + cct.SetMetadata("VFX Notes", clip_attributes) - # # add clip item as take to timeline - # AddTake(cct, startFrame, endFrame) + # reset start timecode of the compound clip + cct.SetClipProperty("Start TC", mp_props["Start TC"]) + + # swap clips on timeline + swap_clips(clip_item, cct, name, in_frame, out_frame) + + cct.SetClipColor("Pink") return cct +def swap_clips(from_clip, to_clip, to_clip_name, to_in_frame, to_out_frame): + """ + Swaping clips on timeline in timelineItem + + It will add take and activate it to the frame range which is inputted + + Args: + from_clip (resolve.mediaPoolItem) + to_clip (resolve.mediaPoolItem) + to_clip_name (str): name of to_clip + to_in_frame (float): cut in frame, usually `GetLeftOffset()` + to_out_frame (float): cut out frame, usually left offset plus duration + + Returns: + bool: True if successfully replaced + + """ + # add clip item as take to timeline + take = from_clip.AddTake( + to_clip, + float(to_in_frame), + float(to_out_frame) + ) + + if not take: + return False + + for take_index in range(1, (int(from_clip.GetTakesCount()) + 1)): + take_item = from_clip.GetTakeByIndex(take_index) + take_mp_item = take_item["mediaPoolItem"] + if to_clip_name in take_mp_item.GetName(): + from_clip.SelectTakeByIndex(take_index) + from_clip.FinalizeTake() + return True + return False + + def validate_tc(x): # Validate and reformat timecode string diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index c48ca3a5a64..c12d5541d47 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,5 +1,7 @@ from pprint import pformat from pype.hosts import resolve +from avalon.vendor import Qt + class CreateShotClip(resolve.Creator): """Publishable clip""" @@ -12,9 +14,8 @@ class CreateShotClip(resolve.Creator): presets = None def process(self): - project = self.project - sequence = self.sequence - presets = self.presets + from pype.hosts.resolve import lib + print(f"__ selected_clips: {self.selected}") # sequence attrs @@ -27,10 +28,11 @@ def process(self): mp_folder = resolve.create_current_sequence_media_bin(self.sequence) print(f"_ mp_folder: {mp_folder.GetName()}") - for t_data in self.selected: + lib.rename_add = 0 + for i, t_data in enumerate(self.selected): + lib.rename_index = i print(t_data) # convert track item to timeline media pool item c_clip = resolve.create_compound_clip( - t_data, mp_folder, presets) - - # replace orig clip with compound_clip + t_data, mp_folder, rename=True, **dict( + {"presets": self.presets})) From 4f3565d2cf85d5ff4171f5247176603ce56642b2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Jun 2020 12:55:17 +0300 Subject: [PATCH 08/23] feat(resolve): basic publish collecting of clips --- pype/hosts/resolve/__init__.py | 4 + pype/hosts/resolve/lib.py | 34 +++- pype/plugins/resolve/publish/collect_clips.py | 159 ++++++++++++++++++ 3 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 pype/plugins/resolve/publish/collect_clips.py diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 5f651f4b298..c8f45259ff9 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -20,6 +20,8 @@ get_current_track_items, create_current_sequence_media_bin, create_compound_clip, + swap_clips, + get_pype_clip_metadata, set_project_manager_to_folder_name ) @@ -61,6 +63,8 @@ "get_current_track_items", "create_current_sequence_media_bin", "create_compound_clip", + "swap_clips", + "get_pype_clip_metadata", "set_project_manager_to_folder_name", # menu diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 50b70241c0c..db3bd989bf4 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,6 +1,7 @@ import sys import json from opentimelineio import opentime +from pprint import pformat from pype.api import Logger @@ -10,6 +11,7 @@ self.pm = None self.rename_index = 0 self.rename_add = 0 +self.pype_metadata_key = "VFX Notes" def get_project_manager(): @@ -46,11 +48,11 @@ def get_current_track_items( selected_clips = list() # get all tracks count filtered by track type - sequence_video_count = sequence.GetTrackCount(track_type) + selected_track_count = sequence.GetTrackCount(track_type) # loop all tracks and get items _clips = dict() - for track_index in range(1, (int(sequence_video_count) + 1)): + for track_index in range(1, (int(selected_track_count) + 1)): track_name = sequence.GetTrackName(track_type, track_index) track_track_items = sequence.GetItemListInTrack( track_type, track_index) @@ -190,7 +192,6 @@ def create_compound_clip(clip_data, folder, rename=False, **kwargs): Returns: resolve.MediaPoolItem: media pool item with compound clip timeline(cct) """ - # get basic objects form data project = clip_data["project"] sequence = clip_data["sequence"] @@ -204,6 +205,7 @@ def create_compound_clip(clip_data, folder, rename=False, **kwargs): # get clip attributes clip_attributes = get_clip_attributes(clip_item) + print(f"_ clip_attributes: {pformat(clip_attributes)}") if rename: presets = kwargs.get("presets") @@ -281,9 +283,11 @@ def create_compound_clip(clip_data, folder, rename=False, **kwargs): project.SetCurrentTimeline(sq_origin) # Add collected metadata and attributes to the comound clip: - if clip_attributes.get("VFX Notes"): - clip_attributes["VFX Notes"] = mp_item.GetMetadata( - "VFX Notes")["VFX Notes"] + if mp_item.GetMetadata(self.pype_metadata_key): + clip_attributes[self.pype_metadata_key] = mp_item.GetMetadata( + self.pype_metadata_key)[self.pype_metadata_key] + + # stringify clip_attributes = json.dumps(clip_attributes) # add attributes to metadata @@ -291,7 +295,7 @@ def create_compound_clip(clip_data, folder, rename=False, **kwargs): cct.SetMetadata(k, v) # add metadata to cct - cct.SetMetadata("VFX Notes", clip_attributes) + cct.SetMetadata(self.pype_metadata_key, clip_attributes) # reset start timecode of the compound clip cct.SetClipProperty("Start TC", mp_props["Start TC"]) @@ -356,6 +360,22 @@ def validate_tc(x): print('Invalid timecode. Try again.') +def get_pype_clip_metadata(clip): + """ + Get pype metadata created by creator plugin + + Attributes: + clip (resolve.TimelineItem): resolve's object + + Returns: + dict: hierarchy, orig clip attributes + """ + mp_item = clip.GetMediaPoolItem() + metadata = mp_item.GetMetadata() + + return metadata.get(self.pype_metadata_key) + + def get_clip_attributes(clip): """ Collect basic atrributes from resolve timeline item diff --git a/pype/plugins/resolve/publish/collect_clips.py b/pype/plugins/resolve/publish/collect_clips.py new file mode 100644 index 00000000000..0f02f26f2e6 --- /dev/null +++ b/pype/plugins/resolve/publish/collect_clips.py @@ -0,0 +1,159 @@ +import os +from pyblish import api +from pype.hosts import resolve +import json + +class CollectClips(api.ContextPlugin): + """Collect all Track items selection.""" + + order = api.CollectorOrder + 0.01 + label = "Collect Clips" + hosts = ["resolve"] + + def process(self, context): + # create asset_names conversion table + if not context.data.get("assetsShared"): + self.log.debug("Created `assetsShared` in context") + context.data["assetsShared"] = dict() + + projectdata = context.data["projectEntity"]["data"] + selection = resolve.get_current_track_items( + filter=True, selecting_color="Pink") + + for clip_data in selection: + data = dict() + + # get basic objects form data + project = clip_data["project"] + sequence = clip_data["sequence"] + clip = clip_data["clip"] + + # sequence attrs + sq_frame_start = sequence.GetStartFrame() + self.log.debug(f"sq_frame_start: {sq_frame_start}") + + sq_markers = sequence.GetMarkers() + + # get details of objects + clip_item = clip["item"] + track = clip_data["track"] + + mp = project.GetMediaPool() + + # get clip attributes + clip_metadata = resolve.get_pype_clip_metadata(clip_item) + clip_metadata = json.loads(clip_metadata) + self.log.debug(f"clip_metadata: {clip_metadata}") + + compound_source_prop = clip_metadata["sourceProperties"] + self.log.debug(f"compound_source_prop: {compound_source_prop}") + + asset_name = clip_item.GetName() + mp_item = clip_item.GetMediaPoolItem() + mp_prop = mp_item.GetClipProperty() + source_first = int(compound_source_prop["Start"]) + source_last = int(compound_source_prop["End"]) + source_duration = compound_source_prop["Frames"] + fps = float(mp_prop["FPS"]) + self.log.debug(f"source_first: {source_first}") + self.log.debug(f"source_last: {source_last}") + self.log.debug(f"source_duration: {source_duration}") + self.log.debug(f"fps: {fps}") + + source_path = os.path.normpath( + compound_source_prop["File Path"]) + source_name = compound_source_prop["File Name"] + source_id = clip_metadata["sourceId"] + self.log.debug(f"source_path: {source_path}") + self.log.debug(f"source_name: {source_name}") + self.log.debug(f"source_id: {source_id}") + + clip_left_offset = int(clip_item.GetLeftOffset()) + clip_right_offset = int(clip_item.GetRightOffset()) + self.log.debug(f"clip_left_offset: {clip_left_offset}") + self.log.debug(f"clip_right_offset: {clip_right_offset}") + + # source in/out + source_in = int(source_first + clip_left_offset) + source_out = int(source_first + clip_right_offset) + self.log.debug(f"source_in: {source_in}") + self.log.debug(f"source_out: {source_out}") + + clip_in = int(clip_item.GetStart() - sq_frame_start) + clip_out = int(clip_item.GetEnd() - sq_frame_start) + clip_duration = int(clip_item.GetDuration()) + self.log.debug(f"clip_in: {clip_in}") + self.log.debug(f"clip_out: {clip_out}") + self.log.debug(f"clip_duration: {clip_duration}") + + is_sequence = False + + self.log.debug( + "__ assets_shared: {}".format( + context.data["assetsShared"])) + + # Check for clips with the same range + # this is for testing if any vertically neighbouring + # clips has been already processed + clip_matching_with_range = next( + (k for k, v in context.data["assetsShared"].items() + if (v.get("_clipIn", 0) == clip_in) + and (v.get("_clipOut", 0) == clip_out) + ), False) + + # check if clip name is the same in matched + # vertically neighbouring clip + # if it is then it is correct and resent variable to False + # not to be rised wrong name exception + if asset_name in str(clip_matching_with_range): + clip_matching_with_range = False + + # rise wrong name exception if found one + assert (not clip_matching_with_range), ( + "matching clip: {asset}" + " timeline range ({clip_in}:{clip_out})" + " conflicting with {clip_matching_with_range}" + " >> rename any of clips to be the same as the other <<" + ).format( + **locals()) + + if ("[" in source_name) and ("]" in source_name): + is_sequence = True + + data.update({ + "name": "_".join([ + track["name"], asset_name, source_name]), + "item": clip_item, + "source": mp_item, + # "timecodeStart": str(source.timecodeStart()), + "timelineStart": sq_frame_start, + "sourcePath": source_path, + "sourceFileHead": source_name, + "isSequence": is_sequence, + "track": track["name"], + "trackIndex": track["index"], + "sourceFirst": source_first, + + "sourceIn": source_in, + "sourceOut": source_out, + "mediaDuration": source_duration, + "clipIn": clip_in, + "clipOut": clip_out, + "clipDuration": clip_duration, + "asset": asset_name, + "subset": "plateMain", + "family": "clip", + "families": [], + "handleStart": projectdata.get("handleStart", 0), + "handleEnd": projectdata.get("handleEnd", 0)}) + + instance = context.create_instance(**data) + + self.log.info("Created instance: {}".format(instance)) + self.log.info("Created instance.data: {}".format(instance.data)) + + context.data["assetsShared"][asset_name] = { + "_clipIn": clip_in, + "_clipOut": clip_out + } + self.log.info("context.data[\"assetsShared\"]: {}".format(context.data["assetsShared"])) From 7465d8cb7394ec1c3987b16da782a8b84f66cfd2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Jun 2020 20:17:22 +0300 Subject: [PATCH 09/23] feat(resolve): creator with sequencial rename gui --- pype/hosts/resolve/menu_style.qss | 8 ++ pype/hosts/resolve/plugin.py | 76 ++++++++++++++++++- .../resolve/create/create_shot_clip.py | 53 ++++++++++++- 3 files changed, 133 insertions(+), 4 deletions(-) diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/menu_style.qss index df4fd7e949d..c43886c7c33 100644 --- a/pype/hosts/resolve/menu_style.qss +++ b/pype/hosts/resolve/menu_style.qss @@ -20,6 +20,14 @@ QPushButton:hover { color: #e64b3d; } +QSpinBox { + background-color: #ffffff; +} + +QLineEdit { + background-color: #ffffff; +} + #PypeMenu { border: 1px solid #fef9ef; } diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 002d12106d6..2eff278b80c 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,8 +1,82 @@ +import sys from avalon import api from pype.hosts import resolve from avalon.vendor import qargparse from pype.api import config +from Qt import QtWidgets + + +class Universal_widget(QtWidgets.QDialog): + def __init__(self, widgets, parent=None): + super(Universal_widget, self).__init__(parent) + + # Where inputs and labels are set + content_widget = QtWidgets.QWidget(self) + content_layout = QtWidgets.QFormLayout(content_widget) + + self.items = dict() + for w in widgets: + attr = getattr(QtWidgets, w["type"]) + label = QtWidgets.QLabel(w["label"]) + attr_name = w["label"].replace(" ", "").lower() + setattr( + self, + attr_name, + attr(parent=self)) + item = getattr(self, attr_name) + func = next((k for k in w if k not in ["label", "type"]), None) + if func: + if getattr(item, func): + func_attr = getattr(item, func) + func_attr(w[func]) + + content_layout.addRow(label, item) + self.items.update({ + w["label"]: item + }) + + # Confirmation buttons + btns_widget = QtWidgets.QWidget(self) + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + + cancel_btn = QtWidgets.QPushButton("Cancel") + btns_layout.addWidget(cancel_btn) + + ok_btn = QtWidgets.QPushButton("Ok") + btns_layout.addWidget(ok_btn) + + # Main layout of the dialog + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + main_layout.addWidget(content_widget) + main_layout.addWidget(btns_widget) + + ok_btn.clicked.connect(self._on_ok_clicked) + cancel_btn.clicked.connect(self._on_cancel_clicked) + + stylesheet = resolve.menu.load_stylesheet() + self.setStyleSheet(stylesheet) + + def _on_ok_clicked(self): + self.value() + self.close() + + def _on_cancel_clicked(self): + self.result = None + self.close() + + def value(self): + for k, v in self.items.items(): + if getattr(v, "value", None): + result = getattr(v, "value") + else: + result = getattr(v, "text") + self.items[k] = result() + self.result = self.items + def get_reference_node_parents(ref): """Return all parent reference nodes of reference node @@ -95,4 +169,4 @@ def __init__(self, *args, **kwargs): else: self.selected = resolve.get_current_track_items(filter=False) - return + self.widget = Universal_widget diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index c12d5541d47..bba9851c0f7 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,6 +1,13 @@ from pprint import pformat from pype.hosts import resolve -from avalon.vendor import Qt +from pype.hosts.resolve import lib +import re + + +def camel_case_split(text): + matches = re.finditer( + '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) + return " ".join([str(m.group(0)).capitalize() for m in matches]) class CreateShotClip(resolve.Creator): @@ -13,11 +20,51 @@ class CreateShotClip(resolve.Creator): presets = None - def process(self): - from pype.hosts.resolve import lib + # widget + layout = [{ + "type": "QLabel", + "label": "Define sequencial rename" + }] + def add_presets_to_layout(self, data): + for k, v in data.items(): + if isinstance(v, dict): + self.layout.append({ + "type": "QLabel", + "label": camel_case_split(k) + }) + self.add_presets_to_layout(v) + elif isinstance(v, str): + self.layout.append({ + "type": "QLineEdit", + "label": camel_case_split(k), + "setText": v + }) + elif isinstance(v, int): + self.layout.append({ + "type": "QSpinBox", + "label": camel_case_split(k), + "setValue": v + }) + + def process(self): print(f"__ selected_clips: {self.selected}") + if len(self.selected) < 1: + return + + self.add_presets_to_layout(self.presets) + + widget = self.widget(self.layout) + widget.exec_() + + print(widget.result) + if widget.result: + print("success") + return + else: + return + # sequence attrs sq_frame_start = self.sequence.GetStartFrame() sq_markers = self.sequence.GetMarkers() From ccadf98c501bc4ee5ae447b846bcc93bb8a6e156 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 25 Jun 2020 09:17:35 +0300 Subject: [PATCH 10/23] feat(resolve): update creator input widget --- pype/hosts/resolve/menu_style.qss | 6 ++++++ pype/hosts/resolve/plugin.py | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/menu_style.qss index c43886c7c33..516c9af72b7 100644 --- a/pype/hosts/resolve/menu_style.qss +++ b/pype/hosts/resolve/menu_style.qss @@ -22,10 +22,16 @@ QPushButton:hover { QSpinBox { background-color: #ffffff; + padding: 5; } QLineEdit { background-color: #ffffff; + padding: 5; +} + +QLabel { + color: #ffffff; } #PypeMenu { diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 2eff278b80c..c1aa05dc7d8 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -4,13 +4,24 @@ from avalon.vendor import qargparse from pype.api import config -from Qt import QtWidgets +from Qt import QtWidgets, QtCore class Universal_widget(QtWidgets.QDialog): def __init__(self, widgets, parent=None): super(Universal_widget, self).__init__(parent) + self.setObjectName("PypeCreatorInput") + + self.setWindowFlags( + QtCore.Qt.Window + | QtCore.Qt.CustomizeWindowHint + | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowStaysOnTopHint + ) + self.setWindowTitle("CreatorInput") + # Where inputs and labels are set content_widget = QtWidgets.QWidget(self) content_layout = QtWidgets.QFormLayout(content_widget) @@ -48,7 +59,7 @@ def __init__(self, widgets, parent=None): # Main layout of the dialog main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setContentsMargins(10, 20, 10, 20) main_layout.setSpacing(0) main_layout.addWidget(content_widget) From 3fb4460b7b412bddff727e438d706c1c567a987f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 26 Jun 2020 14:13:54 +0300 Subject: [PATCH 11/23] feat(resolve): creator plugin with dynamic widget --- pype/hosts/resolve/menu_style.qss | 4 + pype/hosts/resolve/plugin.py | 88 ++++++++++++++----- .../resolve/create/create_shot_clip.py | 39 +------- 3 files changed, 71 insertions(+), 60 deletions(-) diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/menu_style.qss index 516c9af72b7..f3bfa7a30c1 100644 --- a/pype/hosts/resolve/menu_style.qss +++ b/pype/hosts/resolve/menu_style.qss @@ -41,3 +41,7 @@ QLabel { #Spacer { background-color: #282828; } + +#ContentLayout { + background-color: #585858; +} diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index c1aa05dc7d8..8c5fd321d12 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,4 +1,5 @@ import sys +import re from avalon import api from pype.hosts import resolve from avalon.vendor import qargparse @@ -8,7 +9,11 @@ class Universal_widget(QtWidgets.QDialog): - def __init__(self, widgets, parent=None): + + # output items + items = dict() + + def __init__(self, name, presets, parent=None): super(Universal_widget, self).__init__(parent) self.setObjectName("PypeCreatorInput") @@ -24,28 +29,14 @@ def __init__(self, widgets, parent=None): # Where inputs and labels are set content_widget = QtWidgets.QWidget(self) - content_layout = QtWidgets.QFormLayout(content_widget) - - self.items = dict() - for w in widgets: - attr = getattr(QtWidgets, w["type"]) - label = QtWidgets.QLabel(w["label"]) - attr_name = w["label"].replace(" ", "").lower() - setattr( - self, - attr_name, - attr(parent=self)) - item = getattr(self, attr_name) - func = next((k for k in w if k not in ["label", "type"]), None) - if func: - if getattr(item, func): - func_attr = getattr(item, func) - func_attr(w[func]) - - content_layout.addRow(label, item) - self.items.update({ - w["label"]: item - }) + self.content_layout = QtWidgets.QFormLayout(content_widget) + self.content_layout.setObjectName("ContentLayout") + + # first add widget tag line + self.create_row("QLabel", name) + + # add preset data into input widget layout + self.add_presets_to_layout(presets) # Confirmation buttons btns_widget = QtWidgets.QWidget(self) @@ -88,6 +79,57 @@ def value(self): self.items[k] = result() self.result = self.items + def camel_case_split(self, text): + matches = re.finditer( + '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) + return " ".join([str(m.group(0)).capitalize() for m in matches]) + + def create_row(self, type, text, **kwargs): + # get type attribute from qwidgets + attr = getattr(QtWidgets, type) + + # convert label text to normal capitalized text with spaces + label_text = self.camel_case_split(text) + + # assign the new text to lable widget + label = QtWidgets.QLabel(label_text) + + # create attribute name text strip of spaces + attr_name = text.replace(" ", "") + + # create attribute and assign default values + setattr( + self, + attr_name, + attr(parent=self)) + + # assign the created attribute to variable + item = getattr(self, attr_name) + for func, val in kwargs.items(): + if getattr(item, func): + func_attr = getattr(item, func) + func_attr(val) + + self.content_layout.addRow(label, item) + return item + + def add_presets_to_layout(self, data): + for k, v in data.items(): + if isinstance(v, dict): + # if nested dict then create label + # TODO: create also new layout + self.create_row("QLabel", k) + self.add_presets_to_layout(v) + elif isinstance(v, str): + item = self.create_row("QLineEdit", k, setText=v) + elif isinstance(v, int): + item = self.create_row("QSpinBox", k, setValue=v) + + # add it to items for later requests + self.items.update({ + k: item + }) + def get_reference_node_parents(ref): """Return all parent reference nodes of reference node diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index bba9851c0f7..18759a2f98c 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,13 +1,6 @@ from pprint import pformat from pype.hosts import resolve from pype.hosts.resolve import lib -import re - - -def camel_case_split(text): - matches = re.finditer( - '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) - return " ".join([str(m.group(0)).capitalize() for m in matches]) class CreateShotClip(resolve.Creator): @@ -18,44 +11,16 @@ class CreateShotClip(resolve.Creator): icon = "film" defaults = ["Main"] + gui_name = "Define sequencial rename" presets = None - # widget - layout = [{ - "type": "QLabel", - "label": "Define sequencial rename" - }] - - def add_presets_to_layout(self, data): - for k, v in data.items(): - if isinstance(v, dict): - self.layout.append({ - "type": "QLabel", - "label": camel_case_split(k) - }) - self.add_presets_to_layout(v) - elif isinstance(v, str): - self.layout.append({ - "type": "QLineEdit", - "label": camel_case_split(k), - "setText": v - }) - elif isinstance(v, int): - self.layout.append({ - "type": "QSpinBox", - "label": camel_case_split(k), - "setValue": v - }) - def process(self): print(f"__ selected_clips: {self.selected}") if len(self.selected) < 1: return - self.add_presets_to_layout(self.presets) - - widget = self.widget(self.layout) + widget = self.widget(self.gui_name, self.presets) widget.exec_() print(widget.result) From 4391a14da2a2f4063b3692a089b2ae9a1c948040 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 26 Jun 2020 20:28:15 +0300 Subject: [PATCH 12/23] feat(resolve): adding Create widget with style --- pype/hosts/resolve/lib.py | 2 +- pype/hosts/resolve/menu_style.qss | 29 +++-- pype/hosts/resolve/plugin.py | 108 +++++++++++++----- .../resolve/create/create_shot_clip.py | 25 ++-- 4 files changed, 114 insertions(+), 50 deletions(-) diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index db3bd989bf4..deb4fa6339a 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -77,7 +77,7 @@ def get_current_track_items( if filter is True: if selecting_color in ti_color: selected_clips.append(data) - ti.ClearClipColor() + # ti.ClearClipColor() else: selected_clips.append(data) diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/menu_style.qss index f3bfa7a30c1..ea11c4ca2e4 100644 --- a/pype/hosts/resolve/menu_style.qss +++ b/pype/hosts/resolve/menu_style.qss @@ -1,6 +1,7 @@ QWidget { background-color: #282828; border-radius: 3; + font-size: 13px; } QPushButton { @@ -21,27 +22,37 @@ QPushButton:hover { } QSpinBox { - background-color: #ffffff; - padding: 5; + border: 1px solid #090909; + background-color: #201f1f; + color: #ffffff; + padding: 2; + max-width: 8em; + qproperty-alignment: AlignCenter; } QLineEdit { - background-color: #ffffff; - padding: 5; -} - -QLabel { + border: 1px solid #090909; + border-radius: 3px; + background-color: #201f1f; color: #ffffff; + padding: 2; + min-width: 10em; + qproperty-alignment: AlignCenter; } #PypeMenu { border: 1px solid #fef9ef; } -#Spacer { +QVBoxLayout { background-color: #282828; } -#ContentLayout { +#Devider { + border: 1px solid #090909; background-color: #585858; } + +QLabel { + color: #77776b; +} diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 8c5fd321d12..4e7ac80add3 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,4 +1,3 @@ -import sys import re from avalon import api from pype.hosts import resolve @@ -13,10 +12,10 @@ class Universal_widget(QtWidgets.QDialog): # output items items = dict() - def __init__(self, name, presets, parent=None): + def __init__(self, name, info, presets, parent=None): super(Universal_widget, self).__init__(parent) - self.setObjectName("PypeCreatorInput") + self.setObjectName(name) self.setWindowFlags( QtCore.Qt.Window @@ -25,18 +24,25 @@ def __init__(self, name, presets, parent=None): | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint ) - self.setWindowTitle("CreatorInput") + self.setWindowTitle(name or "Pype Creator Input") # Where inputs and labels are set - content_widget = QtWidgets.QWidget(self) - self.content_layout = QtWidgets.QFormLayout(content_widget) - self.content_layout.setObjectName("ContentLayout") + self.content_widget = [QtWidgets.QWidget(self)] + top_layout = QtWidgets.QFormLayout(self.content_widget[0]) + top_layout.setObjectName("ContentLayout") + top_layout.addWidget(Spacer(5, self)) # first add widget tag line - self.create_row("QLabel", name) + top_layout.addWidget(QtWidgets.QLabel(info)) + + top_layout.addWidget(Spacer(5, self)) + + # main dynamic layout + self.content_widget.append(QtWidgets.QWidget(self)) + content_layout = QtWidgets.QFormLayout(self.content_widget[-1]) # add preset data into input widget layout - self.add_presets_to_layout(presets) + self.items = self.add_presets_to_layout(content_layout, presets) # Confirmation buttons btns_widget = QtWidgets.QWidget(self) @@ -50,10 +56,13 @@ def __init__(self, name, presets, parent=None): # Main layout of the dialog main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(10, 20, 10, 20) + main_layout.setContentsMargins(10, 10, 10, 10) main_layout.setSpacing(0) - main_layout.addWidget(content_widget) + # adding content widget + for w in self.content_widget: + main_layout.addWidget(w) + main_layout.addWidget(btns_widget) ok_btn.clicked.connect(self._on_ok_clicked) @@ -63,28 +72,34 @@ def __init__(self, name, presets, parent=None): self.setStyleSheet(stylesheet) def _on_ok_clicked(self): - self.value() + self.result = self.value(self.items) self.close() def _on_cancel_clicked(self): self.result = None self.close() - def value(self): - for k, v in self.items.items(): - if getattr(v, "value", None): + def value(self, data): + for k, v in data.items(): + if isinstance(v, dict): + print(f"nested: {k}") + data[k] = self.value(v) + elif getattr(v, "value", None): + print(f"normal int: {k}") result = getattr(v, "value") + data[k] = result() else: + print(f"normal text: {k}") result = getattr(v, "text") - self.items[k] = result() - self.result = self.items + data[k] = result() + return data def camel_case_split(self, text): matches = re.finditer( '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) return " ".join([str(m.group(0)).capitalize() for m in matches]) - def create_row(self, type, text, **kwargs): + def create_row(self, layout, type, text, **kwargs): # get type attribute from qwidgets attr = getattr(QtWidgets, type) @@ -93,6 +108,7 @@ def create_row(self, type, text, **kwargs): # assign the new text to lable widget label = QtWidgets.QLabel(label_text) + label.setObjectName("LineLabel") # create attribute name text strip of spaces attr_name = text.replace(" ", "") @@ -110,25 +126,57 @@ def create_row(self, type, text, **kwargs): func_attr = getattr(item, func) func_attr(val) - self.content_layout.addRow(label, item) + # add to layout + layout.addRow(label, item) + return item - def add_presets_to_layout(self, data): + def add_presets_to_layout(self, content_layout, data): for k, v in data.items(): if isinstance(v, dict): - # if nested dict then create label - # TODO: create also new layout - self.create_row("QLabel", k) - self.add_presets_to_layout(v) + # adding spacer between sections + self.content_widget.append(QtWidgets.QWidget(self)) + devider = QtWidgets.QVBoxLayout(self.content_widget[-1]) + devider.addWidget(Spacer(5, self)) + devider.setObjectName("Devider") + + # adding nested layout with label + self.content_widget.append(QtWidgets.QWidget(self)) + nested_content_layout = QtWidgets.QFormLayout( + self.content_widget[-1]) + nested_content_layout.setObjectName("NestedContentLayout") + + # add nested key as label + self.create_row(nested_content_layout, "QLabel", k) + data[k] = self.add_presets_to_layout(nested_content_layout, v) elif isinstance(v, str): - item = self.create_row("QLineEdit", k, setText=v) + print(f"layout.str: {k}") + print(f"content_layout: {content_layout}") + data[k] = self.create_row( + content_layout, "QLineEdit", k, setText=v) elif isinstance(v, int): - item = self.create_row("QSpinBox", k, setValue=v) + print(f"layout.int: {k}") + print(f"content_layout: {content_layout}") + data[k] = self.create_row( + content_layout, "QSpinBox", k, setValue=v) + return data + + +class Spacer(QtWidgets.QWidget): + def __init__(self, height, *args, **kwargs): + super(self.__class__, 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) - # add it to items for later requests - self.items.update({ - k: item - }) + self.setLayout(layout) def get_reference_node_parents(ref): diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index 18759a2f98c..43207743e2e 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -11,7 +11,8 @@ class CreateShotClip(resolve.Creator): icon = "film" defaults = ["Main"] - gui_name = "Define sequencial rename" + gui_name = "Pype sequencial rename with hirerarchy" + gui_info = "Define sequencial rename and fill hierarchy data." presets = None def process(self): @@ -20,14 +21,11 @@ def process(self): if len(self.selected) < 1: return - widget = self.widget(self.gui_name, self.presets) + widget = self.widget(self.gui_name, self.gui_info, self.presets) widget.exec_() - print(widget.result) - if widget.result: - print("success") - return - else: + if not widget.result: + print("Operation aborted") return # sequence attrs @@ -43,8 +41,15 @@ def process(self): lib.rename_add = 0 for i, t_data in enumerate(self.selected): lib.rename_index = i - print(t_data) + + # clear color after it is done + t_data["clip"]["item"].ClearClipColor() + # convert track item to timeline media pool item c_clip = resolve.create_compound_clip( - t_data, mp_folder, rename=True, **dict( - {"presets": self.presets})) + t_data, + mp_folder, + rename=True, + **dict( + {"presets": widget.result}) + ) From 72dadc74e744d9ee3f6661507acb3d1218df56cd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Jul 2020 10:05:00 +0200 Subject: [PATCH 13/23] hierachical entities are filtered to avoid deletion of parent and then trying to delete it's children --- .../ftrack/actions/action_delete_asset.py | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delete_asset.py b/pype/modules/ftrack/actions/action_delete_asset.py index 1074efee3b0..f4f6378f1c4 100644 --- a/pype/modules/ftrack/actions/action_delete_asset.py +++ b/pype/modules/ftrack/actions/action_delete_asset.py @@ -534,14 +534,9 @@ def launch(self, session, entities, event): ftrack_proc_txt, ", ".join(ftrack_ids_to_delete) )) - joined_ids_to_delete = ", ".join( - ["\"{}\"".format(id) for id in ftrack_ids_to_delete] + ftrack_ents_to_delete = ( + self._filter_entities_to_delete(ftrack_ids_to_delete, session) ) - ftrack_ents_to_delete = self.session.query( - "select id, link from TypedContext where id in ({})".format( - joined_ids_to_delete - ) - ).all() for entity in ftrack_ents_to_delete: self.session.delete(entity) try: @@ -592,6 +587,40 @@ def launch(self, session, entities, event): return self.report_handle(report_messages, project_name, event) + def _filter_entities_to_delete(self, ftrack_ids_to_delete, session): + """Filter children entities to avoid CircularDependencyError.""" + joined_ids_to_delete = ", ".join( + ["\"{}\"".format(id) for id in ftrack_ids_to_delete] + ) + to_delete_entities = session.query( + "select id, link from TypedContext where id in ({})".format( + joined_ids_to_delete + ) + ).all() + filtered = to_delete_entities[:] + while True: + changed = False + _filtered = filtered[:] + for entity in filtered: + entity_id = entity["id"] + + for _entity in tuple(_filtered): + if entity_id == _entity["id"]: + continue + + for _link in _entity["link"]: + if entity_id == _link["id"] and _entity in _filtered: + _filtered.remove(_entity) + changed = True + break + + filtered = _filtered + + if not changed: + break + + return filtered + def report_handle(self, report_messages, project_name, event): if not report_messages: return { From 900f07d69ec48aa19fc357ca82f53caca0d3a720 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Jul 2020 10:05:20 +0200 Subject: [PATCH 14/23] do not use self.session but session in code --- pype/modules/ftrack/actions/action_delete_asset.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delete_asset.py b/pype/modules/ftrack/actions/action_delete_asset.py index f4f6378f1c4..d09cf73f5fe 100644 --- a/pype/modules/ftrack/actions/action_delete_asset.py +++ b/pype/modules/ftrack/actions/action_delete_asset.py @@ -538,16 +538,16 @@ def launch(self, session, entities, event): self._filter_entities_to_delete(ftrack_ids_to_delete, session) ) for entity in ftrack_ents_to_delete: - self.session.delete(entity) + session.delete(entity) try: - self.session.commit() + session.commit() except Exception: ent_path = "/".join( [ent["name"] for ent in entity["link"]] ) msg = "Failed to delete entity" report_messages[msg].append(ent_path) - self.session.rollback() + session.rollback() self.log.warning( "{} <{}>".format(msg, ent_path), exc_info=True @@ -563,7 +563,7 @@ def launch(self, session, entities, event): for name in asset_names_to_delete ]) # Find assets of selected entities with names of checked subsets - assets = self.session.query(( + assets = session.query(( "select id from Asset where" " context_id in ({}) and name in ({})" ).format(joined_not_deleted, joined_asset_names)).all() @@ -573,11 +573,11 @@ def launch(self, session, entities, event): ", ".join([asset["id"] for asset in assets]) )) for asset in assets: - self.session.delete(asset) + session.delete(asset) try: - self.session.commit() + session.commit() except Exception: - self.session.rollback() + session.rollback() msg = "Failed to delete asset" report_messages[msg].append(asset["id"]) self.log.warning( From 25a1a56dc41598ea9de057ef9734a56b643e9104 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 2 Jul 2020 10:05:54 +0200 Subject: [PATCH 15/23] formatting changes --- pype/modules/ftrack/actions/action_delete_asset.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delete_asset.py b/pype/modules/ftrack/actions/action_delete_asset.py index d09cf73f5fe..27394770e13 100644 --- a/pype/modules/ftrack/actions/action_delete_asset.py +++ b/pype/modules/ftrack/actions/action_delete_asset.py @@ -497,9 +497,8 @@ def launch(self, session, entities, event): for entity in entities: ftrack_id = entity["id"] ftrack_id_name_map[ftrack_id] = entity["name"] - if ftrack_id in ftrack_ids_to_delete: - continue - not_deleted_entities_id.append(ftrack_id) + if ftrack_id not in ftrack_ids_to_delete: + not_deleted_entities_id.append(ftrack_id) mongo_proc_txt = "MongoProcessing: " ftrack_proc_txt = "Ftrack processing: " @@ -581,7 +580,7 @@ def launch(self, session, entities, event): msg = "Failed to delete asset" report_messages[msg].append(asset["id"]) self.log.warning( - "{} <{}>".format(asset["id"]), + "Asset: {} <{}>".format(asset["name"], asset["id"]), exc_info=True ) From b27828704c52d7cc7751b9ee677473edeb5122be Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 9 Jul 2020 15:18:18 +0100 Subject: [PATCH 16/23] Priority was forced to 50 --- pype/plugins/nuke/publish/submit_nuke_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 3731cd25f0d..2b8efb4640c 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -120,7 +120,7 @@ def payload_submit(self, chunk_size = self.deadline_chunk_size priority = instance.data.get("deadlinePriority") - if priority != 50: + if not priority: priority = self.deadline_priority payload = { From 0faf3971635079b1bb6d5a13055e2d172951976b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Jul 2020 16:40:10 +0200 Subject: [PATCH 17/23] tray attributes fix --- pype/tools/tray/pype_tray.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index d73c1cd0ce0..5b1185fa717 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -30,12 +30,15 @@ def __init__(self, tray_widget, main_window): os.path.join(CURRENT_DIR, "modules_imports.json") ) presets = config.get_presets(first_run=True) + menu_items = presets["tray"]["menu_items"] try: - self.modules_usage = presets["tray"]["menu_items"]["item_usage"] + self.modules_usage = menu_items["item_usage"] except Exception: self.modules_usage = {} self.log.critical("Couldn't find modules usage data.") + self.module_attributes = menu_items.get("attributes") or {} + self.icon_run = QtGui.QIcon( resources.get_resource("icons", "circle_green.png") ) @@ -71,19 +74,20 @@ def process_presets(self): if item_usage is None: item_usage = self.modules_usage.get(import_path, True) - if item_usage: - _attributes = attributes.get(title) - if _attributes is None: - _attributes = attributes.get(import_path) - - if _attributes: - item["attributes"] = _attributes - - items.append(item) - else: + if not item_usage: if not title: title = import_path self.log.info("{} - Module ignored".format(title)) + continue + + _attributes = self.module_attributes.get(title) + if _attributes is None: + _attributes = self.module_attributes.get(import_path) + + if _attributes: + item["attributes"] = _attributes + + items.append(item) if items: self.process_items(items, self.tray_widget.menu) From 6fd146b232835cb2be703852f874a6cbe4e7454d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Jul 2020 18:35:25 +0200 Subject: [PATCH 18/23] feat(premiere): adding key to registry from prelaunch --- pype/hooks/premiere/prelaunch.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pype/hooks/premiere/prelaunch.py b/pype/hooks/premiere/prelaunch.py index 118493e9a7f..c0a65c0bf2b 100644 --- a/pype/hooks/premiere/prelaunch.py +++ b/pype/hooks/premiere/prelaunch.py @@ -1,5 +1,6 @@ import os import traceback +import winreg from avalon import api, io, lib from pype.lib import PypeHook from pype.api import Logger, Anatomy @@ -14,6 +15,12 @@ class PremierePrelaunch(PypeHook): shell script. """ project_code = None + reg_string_value = [{ + "path": r"Software\Adobe\CSXS.9", + "name": "PlayerDebugMode", + "type": winreg.REG_SZ, + "value": "1" + }] def __init__(self, logger=None): if not logger: @@ -55,6 +62,10 @@ def execute(self, *args, env: dict = None) -> bool: # adding project code to env env["AVALON_PROJECT_CODE"] = self.project_code + # add keys to registry + self.modify_registry() + + # start avalon try: __import__("pype.hosts.premiere") __import__("pyblish") @@ -69,6 +80,24 @@ def execute(self, *args, env: dict = None) -> bool: return True + def modify_registry(self): + # adding key to registry + for key in self.reg_string_value: + winreg.CreateKey(winreg.HKEY_CURRENT_USER, key["path"]) + rg_key = winreg.OpenKey( + key=winreg.HKEY_CURRENT_USER, + sub_key=key["path"], + reserved=0, + access=winreg.KEY_ALL_ACCESS) + + winreg.SetValueEx( + rg_key, + key["name"], + 0, + key["type"], + key["value"] + ) + def get_anatomy_filled(self): root_path = api.registered_root() project_name = self._S["AVALON_PROJECT"] From 862e233ef919aa215e99dfe1f9f67e9d85ef09a7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Jul 2020 18:35:49 +0200 Subject: [PATCH 19/23] fix(premiere): order of plugins were wrong --- pype/plugins/premiere/publish/collect_frameranges.py | 2 +- .../premiere/publish/collect_instance_representations.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/premiere/publish/collect_frameranges.py b/pype/plugins/premiere/publish/collect_frameranges.py index ffcc1023b53..075f84e8e3d 100644 --- a/pype/plugins/premiere/publish/collect_frameranges.py +++ b/pype/plugins/premiere/publish/collect_frameranges.py @@ -11,7 +11,7 @@ class CollectFrameranges(pyblish.api.InstancePlugin): """ label = "Collect Clip Frameranges" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder - 0.01 families = ['clip'] def process(self, instance): diff --git a/pype/plugins/premiere/publish/collect_instance_representations.py b/pype/plugins/premiere/publish/collect_instance_representations.py index f53c60ad642..b62b47c4735 100644 --- a/pype/plugins/premiere/publish/collect_instance_representations.py +++ b/pype/plugins/premiere/publish/collect_instance_representations.py @@ -12,7 +12,7 @@ class CollectClipRepresentations(pyblish.api.InstancePlugin): """ label = "Collect Clip Representations" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder + 0.1 families = ['clip'] def process(self, instance): From 5af32511575edbe66d393843387ff35801118cc5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Jul 2020 18:37:19 +0200 Subject: [PATCH 20/23] fix(premiere): repair function was not working --- .../plugins/premiere/publish/validate_auto_sync_off.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py index b6429cfa05f..1f3f0b58a5a 100644 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ b/pype/plugins/premiere/publish/validate_auto_sync_off.py @@ -37,13 +37,7 @@ def get_invalid(context): query = 'Project where full_name is "{}"'.format(project_name) project = session.query(query).one() - invalid = None - - if project.get('custom_attributes', {}).get( - 'avalon_auto_sync', False): - invalid = project - - return invalid + return project @classmethod def repair(cls, context): @@ -55,4 +49,4 @@ def repair(cls, context): except Exception: tp, value, tb = sys.exc_info() session.rollback() - six.reraise(tp, value, tb) + raise From 76fe88fbccab959ff8c333dcb88e06e950919a5c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Jul 2020 10:19:58 +0200 Subject: [PATCH 21/23] feat(resolve): config moved to plugins and widget rename --- pype/hosts/resolve/plugin.py | 4 +-- .../resolve/create/create_shot_clip.py | 32 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 4e7ac80add3..29e544cb47d 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -7,7 +7,7 @@ from Qt import QtWidgets, QtCore -class Universal_widget(QtWidgets.QDialog): +class Creator_widget(QtWidgets.QDialog): # output items items = dict() @@ -270,4 +270,4 @@ def __init__(self, *args, **kwargs): else: self.selected = resolve.get_current_track_items(filter=False) - self.widget = Universal_widget + self.widget = Creator_widget diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index 43207743e2e..e4b3e8fe2a4 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -13,17 +13,41 @@ class CreateShotClip(resolve.Creator): gui_name = "Pype sequencial rename with hirerarchy" gui_info = "Define sequencial rename and fill hierarchy data." + gui_inputs = { + "clipName": "{episode}{sequence}{shot}", + "hierarchy": "{folder}/{sequence}/{shot}", + "countFrom": 10, + "steps": 10, + "hierarchyData": { + "folder": "shots", + "shot": "sh####", + "track": "{track}", + "sequence": "sc010", + "episode": "ep01" + } + } presets = None def process(self): - print(f"__ selected_clips: {self.selected}") + # solve gui inputs overwrites from presets + # overwrite gui inputs from presets + for k, v in self.gui_inputs.items(): + if isinstance(v, dict): + # nested dictionary (only one level allowed) + for _k, _v in v.items(): + if self.presets.get(_k): + self.gui_inputs[k][_k] = self.presets[_k] + if self.presets.get(k): + self.gui_inputs[k] = self.presets[k] + + # open widget for plugins inputs + widget = self.widget(self.gui_name, self.gui_info, self.gui_inputs) + widget.exec_() + print(f"__ selected_clips: {self.selected}") if len(self.selected) < 1: return - widget = self.widget(self.gui_name, self.gui_info, self.presets) - widget.exec_() - if not widget.result: print("Operation aborted") return From 309515461aeee31f8d2df4276f7b5942fe240627 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Jul 2020 10:31:11 +0200 Subject: [PATCH 22/23] feat(resolve): hound fixes --- pype/hosts/resolve/plugin.py | 4 ++-- pype/plugins/resolve/publish/collect_clips.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 29e544cb47d..67f2820990b 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -7,7 +7,7 @@ from Qt import QtWidgets, QtCore -class Creator_widget(QtWidgets.QDialog): +class CreatorWidget(QtWidgets.QDialog): # output items items = dict() @@ -270,4 +270,4 @@ def __init__(self, *args, **kwargs): else: self.selected = resolve.get_current_track_items(filter=False) - self.widget = Creator_widget + self.widget = CreatorWidget diff --git a/pype/plugins/resolve/publish/collect_clips.py b/pype/plugins/resolve/publish/collect_clips.py index 0f02f26f2e6..f86e5c8384f 100644 --- a/pype/plugins/resolve/publish/collect_clips.py +++ b/pype/plugins/resolve/publish/collect_clips.py @@ -3,6 +3,7 @@ from pype.hosts import resolve import json + class CollectClips(api.ContextPlugin): """Collect all Track items selection.""" @@ -156,4 +157,6 @@ def process(self, context): "_clipIn": clip_in, "_clipOut": clip_out } - self.log.info("context.data[\"assetsShared\"]: {}".format(context.data["assetsShared"])) + self.log.info( + "context.data[\"assetsShared\"]: {}".format( + context.data["assetsShared"])) From e259a55af9e1efe648842cc3a94bfca6d34c0f62 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Jul 2020 10:42:26 +0200 Subject: [PATCH 23/23] feat(resolve): hound fixes --- pype/hosts/resolve/plugin.py | 6 +++--- pype/hosts/resolve/utility_scripts/test.py | 4 +++- pype/plugins/resolve/create/create_shot_clip.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 67f2820990b..72eec048960 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -13,7 +13,7 @@ class CreatorWidget(QtWidgets.QDialog): items = dict() def __init__(self, name, info, presets, parent=None): - super(Universal_widget, self).__init__(parent) + super(CreatorWidget, self).__init__(parent) self.setObjectName(name) @@ -86,11 +86,11 @@ def value(self, data): data[k] = self.value(v) elif getattr(v, "value", None): print(f"normal int: {k}") - result = getattr(v, "value") + result = v.value() data[k] = result() else: print(f"normal text: {k}") - result = getattr(v, "text") + result = v.text() data[k] = result() return data diff --git a/pype/hosts/resolve/utility_scripts/test.py b/pype/hosts/resolve/utility_scripts/test.py index cf7db3b7e5b..69dc4768bd5 100644 --- a/pype/hosts/resolve/utility_scripts/test.py +++ b/pype/hosts/resolve/utility_scripts/test.py @@ -1,6 +1,8 @@ #! python3 import sys from pype.api import Logger +import DaVinciResolveScript as bmdvr + log = Logger().get_logger(__name__) @@ -10,7 +12,7 @@ def main(): bm = bmdvr.utils.get_resolve_module() log.info(f"blackmagicmodule: {bm}") -import DaVinciResolveScript as bmdvr + print(f"_>> bmdvr.scriptapp(Resolve): {bmdvr.scriptapp('Resolve')}") diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index e4b3e8fe2a4..bd2e013fac1 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -70,10 +70,10 @@ def process(self): t_data["clip"]["item"].ClearClipColor() # convert track item to timeline media pool item - c_clip = resolve.create_compound_clip( + resolve.create_compound_clip( t_data, mp_folder, rename=True, **dict( {"presets": widget.result}) - ) + )