From 737a485530bbac1960ad4fe93bb381daea4cc956 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Jan 2022 17:59:58 +0100 Subject: [PATCH 01/51] flame: fix passing env var and flame version dynamic filling --- .../hosts/flame/api/scripts/wiretap_com.py | 17 +++++++++----- openpype/hosts/flame/hooks/pre_flame_setup.py | 23 +++++++++++++------ .../system_settings/applications.json | 8 ++++--- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/flame/api/scripts/wiretap_com.py b/openpype/hosts/flame/api/scripts/wiretap_com.py index d8dc1884cf..5f7b2580e6 100644 --- a/openpype/hosts/flame/api/scripts/wiretap_com.py +++ b/openpype/hosts/flame/api/scripts/wiretap_com.py @@ -10,14 +10,19 @@ import xml.dom.minidom as minidom from copy import deepcopy import datetime +FLAME_V = os.getenv("OPENPYPE_FLAME_VERSION") + +if not FLAME_V: + raise KeyError("Missing key in environment `OPENPYPE_FLAME_VERSION`") + try: from libwiretapPythonClientAPI import ( WireTapClientInit) except ImportError: - flame_python_path = "/opt/Autodesk/flame_2021/python" + flame_python_path = "/opt/Autodesk/flame_{}/python".format(FLAME_V) flame_exe_path = ( - "/opt/Autodesk/flame_2021/bin/flame.app" - "/Contents/MacOS/startApp") + "/opt/Autodesk/flame_{}/bin/flame.app" + "/Contents/MacOS/startApp").format(FLAME_V) sys.path.append(flame_python_path) @@ -169,7 +174,7 @@ class WireTapCom(object): # check if volumes exists if self.volume_name not in volumes: raise AttributeError( - ("Volume '{}' does not exist '{}'").format( + ("Volume '{}' does not exist in '{}'").format( self.volume_name, volumes) ) @@ -179,7 +184,7 @@ class WireTapCom(object): "/opt/Autodesk/", "wiretap", "tools", - "2021", + FLAME_V, "wiretap_create_node", ), '-n', @@ -434,7 +439,7 @@ class WireTapCom(object): "/opt/Autodesk/", "wiretap", "tools", - "2021", + FLAME_V, "wiretap_duplicate_node", ), "-s", diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 159fb37410..e7ef856907 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -19,7 +19,10 @@ class FlamePrelaunch(PreLaunchHook): app_groups = ["flame"] # todo: replace version number with avalon launch app version - flame_python_exe = "/opt/Autodesk/python/2021/bin/python2.7" + flame_python_exe = ( + "/opt/Autodesk/python/{OPENPYPE_FLAME_VERSION}" + "/bin/python2.7" + ) wtc_script_path = os.path.join( opflame.HOST_DIR, "api", "scripts", "wiretap_com.py") @@ -30,6 +33,7 @@ class FlamePrelaunch(PreLaunchHook): self.signature = "( {} )".format(self.__class__.__name__) def execute(self): + _env = self.launch_context.env """Hook entry method.""" project_doc = self.data["project_doc"] user_name = get_openpype_username() @@ -58,9 +62,9 @@ class FlamePrelaunch(PreLaunchHook): data_to_script = { # from settings - "host_name": os.getenv("FLAME_WIRETAP_HOSTNAME") or hostname, - "volume_name": os.getenv("FLAME_WIRETAP_VOLUME"), - "group_name": os.getenv("FLAME_WIRETAP_GROUP"), + "host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname, + "volume_name": _env.get("FLAME_WIRETAP_VOLUME"), + "group_name": _env.get("FLAME_WIRETAP_GROUP"), "color_policy": "ACES 1.1", # from project @@ -68,9 +72,12 @@ class FlamePrelaunch(PreLaunchHook): "user_name": user_name, "project_data": project_data } + + self.log.info(pformat(dict(_env))) + self.log.info(pformat(data_to_script)) + app_arguments = self._get_launch_arguments(data_to_script) - self.log.info(pformat(dict(self.launch_context.env))) opflame.setup(self.launch_context.env) @@ -83,7 +90,9 @@ class FlamePrelaunch(PreLaunchHook): with make_temp_file(dumped_script_data) as tmp_json_path: # Prepare subprocess arguments args = [ - self.flame_python_exe, + self.flame_python_exe.format( + **self.launch_context.env + ), self.wtc_script_path, tmp_json_path ] @@ -91,7 +100,7 @@ class FlamePrelaunch(PreLaunchHook): process_kwargs = { "logger": self.log, - "env": {} + "env": self.launch_context.env } openpype.api.run_subprocess(args, **process_kwargs) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 1cbe09f576..23ea64fdc1 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -118,10 +118,10 @@ "executables": { "windows": [], "darwin": [ - "/opt/Autodesk/flame_2021/bin/flame.app/Contents/MacOS/startApp" + "/opt/Autodesk/flame_{OPENPYPE_FLAME_VERSION}/bin/flame.app/Contents/MacOS/startApp" ], "linux": [ - "/opt/Autodesk/flame_2021/bin/flame" + "/opt/Autodesk/flame_{OPENPYPE_FLAME_VERSION}/bin/flame" ] }, "arguments": { @@ -129,7 +129,9 @@ "darwin": [], "linux": [] }, - "environment": {} + "environment": { + "OPENPYPE_FLAME_VERSION": "2021" + } }, "__dynamic_keys_labels__": { "2021": "2021 (Testing Only)" From 1c865567f5b88061aff9bda6a3cd4ef4662a6034 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Jan 2022 11:12:21 +0100 Subject: [PATCH 02/51] flame path no env var --- .../defaults/system_settings/applications.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 23ea64fdc1..3a097d2b37 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -118,10 +118,10 @@ "executables": { "windows": [], "darwin": [ - "/opt/Autodesk/flame_{OPENPYPE_FLAME_VERSION}/bin/flame.app/Contents/MacOS/startApp" + "/opt/Autodesk/flame_2021/bin/flame.app/Contents/MacOS/startApp" ], "linux": [ - "/opt/Autodesk/flame_{OPENPYPE_FLAME_VERSION}/bin/flame" + "/opt/Autodesk/flame_2021/bin/flame" ] }, "arguments": { @@ -144,7 +144,10 @@ "icon": "{}/app_icons/nuke.png", "host_name": "nuke", "environment": { - "NUKE_PATH": ["{NUKE_PATH}", "{OPENPYPE_STUDIO_PLUGINS}/nuke"] + "NUKE_PATH": [ + "{NUKE_PATH}", + "{OPENPYPE_STUDIO_PLUGINS}/nuke" + ] }, "variants": { "13-0": { @@ -250,7 +253,10 @@ "icon": "{}/app_icons/nuke.png", "host_name": "nuke", "environment": { - "NUKE_PATH": ["{NUKE_PATH}", "{OPENPYPE_STUDIO_PLUGINS}/nuke"] + "NUKE_PATH": [ + "{NUKE_PATH}", + "{OPENPYPE_STUDIO_PLUGINS}/nuke" + ] }, "variants": { "13-0": { From b3a284294949189a3737ed28fac90294a8829087 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Jan 2022 11:29:37 +0100 Subject: [PATCH 03/51] flame: fixing permission issue overcoming --- openpype/hosts/flame/api/utils.py | 38 +++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index 201c7d2fac..aae102dd7e 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -75,10 +75,19 @@ def _sync_utility_scripts(env=None): path = os.path.join(flame_shared_dir, _itm) log.info("Removing `{path}`...".format(**locals())) - if os.path.isdir(path): - shutil.rmtree(path, onerror=None) - else: - os.remove(path) + + try: + if os.path.isdir(path): + shutil.rmtree(path, onerror=None) + else: + os.remove(path) + except PermissionError as msg: + log.warning( + "Not able to remove: `{}`, Problem with: `{}`".format( + path, + msg + ) + ) # copy scripts into Resolve's utility scripts dir for dirpath, scriptlist in scripts.items(): @@ -88,13 +97,22 @@ def _sync_utility_scripts(env=None): src = os.path.join(dirpath, _script) dst = os.path.join(flame_shared_dir, _script) log.info("Copying `{src}` to `{dst}`...".format(**locals())) - if os.path.isdir(src): - shutil.copytree( - src, dst, symlinks=False, - ignore=None, ignore_dangling_symlinks=False + + try: + if os.path.isdir(src): + shutil.copytree( + src, dst, symlinks=False, + ignore=None, ignore_dangling_symlinks=False + ) + else: + shutil.copy2(src, dst) + except PermissionError as msg: + log.warning( + "Not able to coppy to: `{}`, Problem with: `{}`".format( + dst, + msg + ) ) - else: - shutil.copy2(src, dst) def setup(env=None): From 97f6afa90fb9bf6d5f4acbb1a41b0b80c730b8a8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Jan 2022 14:36:21 +0100 Subject: [PATCH 04/51] flame: fixing pref file handling --- openpype/hosts/flame/api/lib.py | 9 ++++++--- openpype/hosts/flame/api/utils.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 96bffab774..44043c00f2 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -114,11 +114,14 @@ class FlameAppFramework(object): self.hostname, ) - self.log.info("[{}] waking up".format(self.__class__.__name__)) - self.load_prefs() + self.log.info("[{}] waking up".format(self.__class__.__name__)) + + try: + self.load_prefs() + except RuntimeError: + self.save_prefs() # menu auto-refresh defaults - if not self.prefs_global.get("menu_auto_refresh"): self.prefs_global["menu_auto_refresh"] = { "media_panel": True, diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index aae102dd7e..8ed8613b15 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -106,7 +106,7 @@ def _sync_utility_scripts(env=None): ) else: shutil.copy2(src, dst) - except PermissionError as msg: + except (PermissionError, FileExistsError) as msg: log.warning( "Not able to coppy to: `{}`, Problem with: `{}`".format( dst, From 723bfb2b3cc879c09f3ccdc2b89316e6ebfeb4c0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Jan 2022 18:04:06 +0100 Subject: [PATCH 05/51] flame: adding openpype marker on segment handling --- openpype/hosts/flame/api/lib.py | 152 +++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 44043c00f2..f91f593eb5 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -1,5 +1,6 @@ import sys import os +import json import pickle import contextlib from pprint import pformat @@ -8,6 +9,12 @@ from openpype.api import Logger log = Logger().get_logger(__name__) +class ctx: + # OpenPype marker workflow variables + marker_name = "OpenPypeData" + marker_duration = 0 + marker_color = (0.0, 1.0, 1.0) + publish_default = False @contextlib.contextmanager def io_preferences_file(klass, filepath, write=False): @@ -114,7 +121,7 @@ class FlameAppFramework(object): self.hostname, ) - self.log.info("[{}] waking up".format(self.__class__.__name__)) + self.log.info("[{}] waking up".format(self.__class__.__name__)) try: self.load_prefs() @@ -337,3 +344,146 @@ def get_metadata(project_name, _log=None): policy_wiretap = GetProjectColorPolicy(_log=_log) return policy_wiretap.process(project_name) + + +def get_segment_pype_tag(segment, with_marker=None): + """ + Get openpype track item tag created by creator or loader plugin. + + Attributes: + segment (flame.PySegment): flame api object + with_marker (bool)[optional]: if true it will return also marker object + + Returns: + dict: openpype tag data + + Returns(with_marker=True): + flame.PyMarker, dict + """ + for marker in segment.markers: + comment = marker.comment.get_value() + color = marker.colour.get_value() + name = marker.name.get_value() + + if name == ctx.marker_name and color == ctx.marker_color: + if not with_marker: + return json.loads(comment) + else: + return marker, json.loads(comment) + + +def set_segment_pype_tag(segment, data=None): + """ + Set openpype track item tag to input segment. + + Attributes: + segment (flame.PySegment): flame api object + + Returns: + dict: json loaded data + """ + data = data or dict() + + marker_data = get_segment_pype_tag(segment, True) + + if marker_data: + # get available openpype tag if any + marker, tag_data = marker_data + # update tag data with new data + tag_data.update(data) + # update marker with tag data + marker.comment = json.dumps(tag_data) + + return True + else: + # update tag data with new data + marker = create_pype_marker(segment) + # add tag data to marker's comment + marker.comment = json.dumps(data) + + return True + + + +def imprint(segment, data=None): + """ + Adding openpype data to Flame timeline segment. + + Also including publish attribute into tag. + + Arguments: + segment (flame.PySegment)): flame api object + data (dict): Any data which needst to be imprinted + + Examples: + data = { + 'asset': 'sq020sh0280', + 'family': 'render', + 'subset': 'subsetMain' + } + """ + data = data or {} + + if not set_segment_pype_tag(segment, data): + raise AttributeError("Not imprint data to segment") + + # add publish attribute + set_publish_attribute(segment, True) + + +def set_publish_attribute(segment, value): + """ Set Publish attribute in input Tag object + + Attribute: + segment (flame.PySegment)): flame api object + value (bool): True or False + """ + tag_data = get_segment_pype_tag(segment) + tag_data["publish"] = value + + # set data to the publish attribute + if not set_segment_pype_tag(segment, tag_data): + raise AttributeError("Not imprint data to segment") + + +def get_publish_attribute(segment): + """ Get Publish attribute from input Tag object + + Attribute: + segment (flame.PySegment)): flame api object + + Returns: + bool: True or False + """ + tag_data = get_segment_pype_tag(segment) + + if not tag_data: + set_publish_attribute(segment, ctx.publish_default) + return ctx.publish_default + + return tag_data["publish"] + + +def create_pype_marker(segment): + """ Create openpype marker on a segment. + + Attributes: + segment (flame.PySegment): flame api object + + Returns: + flame.PyMarker: flame api object + """ + # get duration of segment + duration = segment.record_duration.relative_frame + # calculate start frame of the new marker + start_frame = int(segment.record_in.relative_frame) + int(duration / 2) + # create marker + marker = segment.create_marker(start_frame) + # set marker name + marker.name = ctx.marker_name + # set duration + marker.duration = ctx.marker_duration + # set colour + marker.colour = ctx.marker_color + + return marker \ No newline at end of file From 9d0e3363c5bfab9741ee2be01ff3e5f56d5b59cf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Jan 2022 21:44:29 +0100 Subject: [PATCH 06/51] flame: testing in publish plugin --- .../plugins/publish/collect_test_selection.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 9a80a92414..d30d6ed331 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -24,3 +24,24 @@ class CollectTestSelection(pyblish.api.ContextPlugin): otio_timeline = otio_export.create_otio_timeline(sequence) self.log.info(pformat(otio_timeline)) + + # test segment markers + for ver in sequence.versions: + for track in ver.tracks: + if len(track.segments) == 0 and track.hidden: + continue + + for segment in track.segments: + if str(segment.name)[1:-1] == "": + continue + if not segment.selected: + continue + + self.log.debug("Segment with OpenPypeData: {}".format( + segment.name)) + + lib.imprint(segment, { + 'asset': 'sq020sh0280', + 'family': 'render', + 'subset': 'subsetMain' + }) From 5d39784abb1228bddbb5abf0fc329c5d3d4aecfc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Jan 2022 22:32:07 +0100 Subject: [PATCH 07/51] flame: maintained segment selection --- openpype/hosts/flame/api/lib.py | 45 +++++++++++++++++++++++++--- openpype/hosts/flame/api/pipeline.py | 26 ---------------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index f91f593eb5..5860bb728d 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -393,15 +393,13 @@ def set_segment_pype_tag(segment, data=None): tag_data.update(data) # update marker with tag data marker.comment = json.dumps(tag_data) - - return True else: # update tag data with new data marker = create_pype_marker(segment) # add tag data to marker's comment marker.comment = json.dumps(data) - return True + return True @@ -486,4 +484,43 @@ def create_pype_marker(segment): # set colour marker.colour = ctx.marker_color - return marker \ No newline at end of file + return marker + + +@contextlib.contextmanager +def maintained_segment_selection(sequence): + """Maintain selection during context + + Example: + >>> with maintained_selection(): + ... node['selected'].setValue(True) + >>> print(node['selected'].value()) + False + """ + selected_segments = [] + for ver in sequence.versions: + for track in ver.tracks: + if len(track.segments) == 0 and track.hidden: + continue + for segment in track.segments: + if segment.selected != True: + continue + selected_segments.append(segment) + try: + # do the operation + yield + finally: + reset_segment_selection(sequence) + for segment in selected_segments: + segment.selected = True + + +def reset_segment_selection(sequence): + """Deselect all selected nodes + """ + for ver in sequence.versions: + for track in ver.tracks: + if len(track.segments) == 0 and track.hidden: + continue + for segment in track.segments: + segment.selected = False diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 26dfe7c032..00860857f1 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -97,32 +97,6 @@ def update_container(tl_segment, data=None): # TODO: update_container pass - -@contextlib.contextmanager -def maintained_selection(): - """Maintain selection during context - - Example: - >>> with maintained_selection(): - ... node['selected'].setValue(True) - >>> print(node['selected'].value()) - False - """ - # TODO: maintained_selection + remove undo steps - - try: - # do the operation - yield - finally: - pass - - -def reset_selection(): - """Deselect all selected nodes - """ - pass - - def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle node passthrough states on instance toggles.""" From 655e85f12ad5c4e98b777c1f90e285fb838046a8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Jan 2022 22:46:37 +0100 Subject: [PATCH 08/51] flame: adding docstring to maintained segment selection --- openpype/hosts/flame/api/lib.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 5860bb728d..0ba6d81c0d 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -491,26 +491,41 @@ def create_pype_marker(segment): def maintained_segment_selection(sequence): """Maintain selection during context + Attributes: + sequence (flame.PySequence): python api object + + Yield: + list of flame.PySegment + Example: - >>> with maintained_selection(): - ... node['selected'].setValue(True) - >>> print(node['selected'].value()) - False + >>> with maintained_segment_selection(sequence) as selected_segments: + ... for segment in selected_segments: + ... segment.selected = False + >>> print(segment.selected) + True """ selected_segments = [] + # loop versions in sequence for ver in sequence.versions: + # loop track in versions for track in ver.tracks: + # ignore all empty tracks and hidden too if len(track.segments) == 0 and track.hidden: continue + # loop all segment in remaining tracks for segment in track.segments: + # ignore all segments not selected if segment.selected != True: continue + # add it to original selection selected_segments.append(segment) try: - # do the operation - yield + # do the operation on selected segments + yield selected_segments finally: + # reset all selected clips reset_segment_selection(sequence) + # select only original selection of segments for segment in selected_segments: segment.selected = True From 152810f09d8eb61f17f39535819bcc789625f113 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 10:39:43 +0100 Subject: [PATCH 09/51] flame: moving and renaming api function --- openpype/hosts/flame/__init__.py | 15 +++++++++- openpype/hosts/flame/api/lib.py | 43 ++++++---------------------- openpype/hosts/flame/api/pipeline.py | 31 ++++++++++++++++++-- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index da28170679..d6019100cb 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -8,7 +8,6 @@ from .api.pipeline import ( ls, containerise, update_container, - maintained_selection, remove_instance, list_instances, imprint @@ -21,6 +20,13 @@ from .api.lib import ( get_current_project, get_current_sequence, create_bin, + create_segment_data_marker, + get_segment_data_marker, + set_segment_data_marker, + set_publish_attribute, + get_publish_attribute, + maintained_segment_selection, + reset_segment_selection ) from .api.menu import ( @@ -90,6 +96,13 @@ __all__ = [ "get_current_project", "get_current_sequence", "create_bin", + "create_segment_data_marker", + "get_segment_data_marker", + "set_segment_data_marker", + "set_publish_attribute", + "get_publish_attribute", + "maintained_segment_selection", + "reset_segment_selection", # menu "FlameMenuProjectConnect", diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 0ba6d81c0d..03b4c1f619 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -346,7 +346,7 @@ def get_metadata(project_name, _log=None): return policy_wiretap.process(project_name) -def get_segment_pype_tag(segment, with_marker=None): +def get_segment_data_marker(segment, with_marker=None): """ Get openpype track item tag created by creator or loader plugin. @@ -372,7 +372,7 @@ def get_segment_pype_tag(segment, with_marker=None): return marker, json.loads(comment) -def set_segment_pype_tag(segment, data=None): +def set_segment_data_marker(segment, data=None): """ Set openpype track item tag to input segment. @@ -384,7 +384,7 @@ def set_segment_pype_tag(segment, data=None): """ data = data or dict() - marker_data = get_segment_pype_tag(segment, True) + marker_data = get_segment_data_marker(segment, True) if marker_data: # get available openpype tag if any @@ -395,40 +395,13 @@ def set_segment_pype_tag(segment, data=None): marker.comment = json.dumps(tag_data) else: # update tag data with new data - marker = create_pype_marker(segment) + marker = create_segment_data_marker(segment) # add tag data to marker's comment marker.comment = json.dumps(data) return True - -def imprint(segment, data=None): - """ - Adding openpype data to Flame timeline segment. - - Also including publish attribute into tag. - - Arguments: - segment (flame.PySegment)): flame api object - data (dict): Any data which needst to be imprinted - - Examples: - data = { - 'asset': 'sq020sh0280', - 'family': 'render', - 'subset': 'subsetMain' - } - """ - data = data or {} - - if not set_segment_pype_tag(segment, data): - raise AttributeError("Not imprint data to segment") - - # add publish attribute - set_publish_attribute(segment, True) - - def set_publish_attribute(segment, value): """ Set Publish attribute in input Tag object @@ -436,11 +409,11 @@ def set_publish_attribute(segment, value): segment (flame.PySegment)): flame api object value (bool): True or False """ - tag_data = get_segment_pype_tag(segment) + tag_data = get_segment_data_marker(segment) tag_data["publish"] = value # set data to the publish attribute - if not set_segment_pype_tag(segment, tag_data): + if not set_segment_data_marker(segment, tag_data): raise AttributeError("Not imprint data to segment") @@ -453,7 +426,7 @@ def get_publish_attribute(segment): Returns: bool: True or False """ - tag_data = get_segment_pype_tag(segment) + tag_data = get_segment_data_marker(segment) if not tag_data: set_publish_attribute(segment, ctx.publish_default) @@ -462,7 +435,7 @@ def get_publish_attribute(segment): return tag_data["publish"] -def create_pype_marker(segment): +def create_segment_data_marker(segment): """ Create openpype marker on a segment. Attributes: diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 00860857f1..2295589627 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -5,6 +5,10 @@ import contextlib from avalon import api as avalon from pyblish import api as pyblish from openpype.api import Logger +from .lib import ( + set_segment_data_marker, + set_publish_attribute +) AVALON_CONTAINERS = "AVALON_CONTAINERS" @@ -124,6 +128,27 @@ def list_instances(): pass -def imprint(item, data=None): - # TODO: imprint - pass +def imprint(segment, data=None): + """ + Adding openpype data to Flame timeline segment. + + Also including publish attribute into tag. + + Arguments: + segment (flame.PySegment)): flame api object + data (dict): Any data which needst to be imprinted + + Examples: + data = { + 'asset': 'sq020sh0280', + 'family': 'render', + 'subset': 'subsetMain' + } + """ + data = data or {} + + if not set_segment_data_marker(segment, data): + raise AttributeError("Not imprint data to segment") + + # add publish attribute + set_publish_attribute(segment, True) From 455a8a50b1d2ca98f33c6661c1e654886c53563a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 13:02:58 +0100 Subject: [PATCH 10/51] flame: add lib functionalities for segment operations --- openpype/hosts/flame/__init__.py | 6 +- openpype/hosts/flame/api/lib.py | 124 ++++++++++++++++++++++++++----- 2 files changed, 109 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index d6019100cb..691b6f8119 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -25,8 +25,10 @@ from .api.lib import ( set_segment_data_marker, set_publish_attribute, get_publish_attribute, + get_sequence_segments, maintained_segment_selection, - reset_segment_selection + reset_segment_selection, + get_segment_attributes ) from .api.menu import ( @@ -101,8 +103,10 @@ __all__ = [ "set_segment_data_marker", "set_publish_attribute", "get_publish_attribute", + "get_sequence_segments", "maintained_segment_selection", "reset_segment_selection", + "get_segment_attributes" # menu "FlameMenuProjectConnect", diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 03b4c1f619..2d30390d21 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -1,5 +1,6 @@ import sys import os +import re import json import pickle import contextlib @@ -13,8 +14,21 @@ class ctx: # OpenPype marker workflow variables marker_name = "OpenPypeData" marker_duration = 0 - marker_color = (0.0, 1.0, 1.0) + marker_color = "red" publish_default = False + color_map = { + "red": (1.0, 0.0, 0.0), + "orange": (1.0, 0.5, 0.0), + "yellow": (1.0, 1.0, 0.0), + "pink": (1.0, 0.5, 1.0), + "white": (1.0, 1.0, 1.0), + "green": (0.0, 1.0, 0.0), + "cyan": (0.0, 1.0, 1.0), + "blue": (0.0, 0.0, 1.0), + "purple": (0.5, 0.0, 0.5), + "magenta": (0.5, 0.0, 1.0), + "black": (0.0, 0.0, 0.0) +} @contextlib.contextmanager def io_preferences_file(klass, filepath, write=False): @@ -262,8 +276,8 @@ def get_media_storage(): def get_current_project(): - # TODO: get_current_project - return + import flame + return flame.project.current_project def get_current_sequence(selection): @@ -365,7 +379,7 @@ def get_segment_data_marker(segment, with_marker=None): color = marker.colour.get_value() name = marker.name.get_value() - if name == ctx.marker_name and color == ctx.marker_color: + if name == ctx.marker_name and color == ctx.color_map[ctx.marker_color]: if not with_marker: return json.loads(comment) else: @@ -455,10 +469,28 @@ def create_segment_data_marker(segment): # set duration marker.duration = ctx.marker_duration # set colour - marker.colour = ctx.marker_color + marker.colour = ctx.color_map[ctx.marker_color] # Red return marker +def get_sequence_segments(sequence, selected=False): + segments = [] + # loop versions in sequence + for ver in sequence.versions: + # loop track in versions + for track in ver.tracks: + # ignore all empty tracks and hidden too + if len(track.segments) == 0 and track.hidden: + continue + # loop all segment in remaining tracks + for segment in track.segments: + # ignore all segments not selected + if segment.selected != True and selected == True: + continue + # add it to original selection + segments.append(segment) + return segments + @contextlib.contextmanager def maintained_segment_selection(sequence): @@ -477,21 +509,7 @@ def maintained_segment_selection(sequence): >>> print(segment.selected) True """ - selected_segments = [] - # loop versions in sequence - for ver in sequence.versions: - # loop track in versions - for track in ver.tracks: - # ignore all empty tracks and hidden too - if len(track.segments) == 0 and track.hidden: - continue - # loop all segment in remaining tracks - for segment in track.segments: - # ignore all segments not selected - if segment.selected != True: - continue - # add it to original selection - selected_segments.append(segment) + selected_segments = get_sequence_segments(sequence, True) try: # do the operation on selected segments yield selected_segments @@ -512,3 +530,69 @@ def reset_segment_selection(sequence): continue for segment in track.segments: segment.selected = False + + +def _get_shot_tokens_values(clip, tokens): + old_value = None + output = {} + + if not clip.shot_name: + return output + + old_value = clip.shot_name.get_value() + + for token in tokens: + clip.shot_name.set_value(token) + _key = str(re.sub("[<>]", "", token)).replace(" ", "_") + + try: + output[_key] = int(clip.shot_name.get_value()) + except ValueError: + output[_key] = clip.shot_name.get_value() + + clip.shot_name.set_value(old_value) + + return output + + +def get_segment_attributes(segment): + if str(segment.name)[1:-1] == "": + return None + + # Add timeline segment to tree + clip_data = { + "segment_name": segment.name.get_value(), + "segment_comment": segment.comment.get_value(), + "tape_name": segment.tape_name, + "source_name": segment.source_name, + "fpath": segment.file_path, + "PySegment": segment + } + + # add all available shot tokens + shot_tokens = _get_shot_tokens_values(segment, [ + "", "", "", "", "", + "", "" + ]) + clip_data.update(shot_tokens) + + # populate shot source metadata + segment_attrs = [ + "record_duration", "record_in", "record_out", + "source_duration", "source_in", "source_out" + ] + segment_attrs_data = {} + for attr in segment_attrs: + if not hasattr(segment, attr): + continue + _value = getattr(segment, attr) + segment_attrs_data[attr] = str(_value).replace("+", ":") + + if attr in ["record_in", "record_out"]: + clip_data[attr] = _value.relative_frame + else: + clip_data[attr] = _value.frame + + clip_data["segment_timecodes"] = segment_attrs_data + + return clip_data From 8f721d3360ca3a445d9dd6ea10ec9c2aac64249f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 13:46:21 +0100 Subject: [PATCH 11/51] flame: create plugin abstractions --- openpype/hosts/flame/api/plugin.py | 623 +++++++++++++++++++++++++++++ openpype/hosts/flame/api/style.css | 26 ++ 2 files changed, 649 insertions(+) create mode 100644 openpype/hosts/flame/api/style.css diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 2a28a20a75..1a3880a19a 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -1,3 +1,626 @@ +import re +import os +from Qt import QtWidgets, QtCore +import openpype.api as openpype +import openpype.hosts.flame as opflame +from . import lib +from copy import deepcopy + +log = openpype.Logger().get_logger(__name__) + # Creator plugin functions +def load_stylesheet(): + path = os.path.join(os.path.dirname(__file__), "style.css") + if not os.path.exists(path): + log.warning("Unable to load stylesheet, file not found in resources") + return "" + + with open(path, "r") as file_stream: + stylesheet = file_stream.read() + return stylesheet + + +class CreatorWidget(QtWidgets.QDialog): + + # output items + items = dict() + + def __init__(self, name, info, ui_inputs, parent=None): + super(CreatorWidget, self).__init__(parent) + + self.setObjectName(name) + + self.setWindowFlags( + QtCore.Qt.Window + | QtCore.Qt.CustomizeWindowHint + | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowStaysOnTopHint + ) + self.setWindowTitle(name or "Pype Creator Input") + self.resize(500, 700) + + # Where inputs and labels are set + 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 + top_layout.addWidget(QtWidgets.QLabel(info)) + + # main dynamic layout + self.scroll_area = QtWidgets.QScrollArea(self, widgetResizable=True) + self.scroll_area.setVerticalScrollBarPolicy( + QtCore.Qt.ScrollBarAsNeeded) + self.scroll_area.setVerticalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOn) + self.scroll_area.setHorizontalScrollBarPolicy( + QtCore.Qt.ScrollBarAlwaysOff) + self.scroll_area.setWidgetResizable(True) + + self.content_widget.append(self.scroll_area) + + scroll_widget = QtWidgets.QWidget(self) + in_scroll_area = QtWidgets.QVBoxLayout(scroll_widget) + self.content_layout = [in_scroll_area] + + # add preset data into input widget layout + self.items = self.populate_widgets(ui_inputs) + self.scroll_area.setWidget(scroll_widget) + + # 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(10, 10, 10, 10) + main_layout.setSpacing(0) + + # 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) + cancel_btn.clicked.connect(self._on_cancel_clicked) + + stylesheet = load_stylesheet() + self.setStyleSheet(stylesheet) + + def _on_ok_clicked(self): + self.result = self.value(self.items) + self.close() + + def _on_cancel_clicked(self): + self.result = None + self.close() + + def value(self, data, new_data=None): + new_data = new_data or dict() + for k, v in data.items(): + new_data[k] = { + "target": None, + "value": None + } + if v["type"] == "dict": + new_data[k]["target"] = v["target"] + new_data[k]["value"] = self.value(v["value"]) + if v["type"] == "section": + new_data.pop(k) + new_data = self.value(v["value"], new_data) + elif getattr(v["value"], "currentText", None): + new_data[k]["target"] = v["target"] + new_data[k]["value"] = v["value"].currentText() + elif getattr(v["value"], "isChecked", None): + new_data[k]["target"] = v["target"] + new_data[k]["value"] = v["value"].isChecked() + elif getattr(v["value"], "value", None): + new_data[k]["target"] = v["target"] + new_data[k]["value"] = v["value"].value() + elif getattr(v["value"], "text", None): + new_data[k]["target"] = v["target"] + new_data[k]["value"] = v["value"].text() + + return new_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, layout, 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) + label.setObjectName("LineLabel") + + # 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) + + # add to layout + layout.addRow(label, item) + + return item + + def populate_widgets(self, data, content_layout=None): + """ + Populate widget from input dict. + + Each plugin has its own set of widget rows defined in dictionary + each row values should have following keys: `type`, `target`, + `label`, `order`, `value` and optionally also `toolTip`. + + Args: + data (dict): widget rows or organized groups defined + by types `dict` or `section` + content_layout (QtWidgets.QFormLayout)[optional]: used when nesting + + Returns: + dict: redefined data dict updated with created widgets + + """ + + content_layout = content_layout or self.content_layout[-1] + # fix order of process by defined order value + ordered_keys = list(data.keys()) + for k, v in data.items(): + try: + # try removing a key from index which should + # be filled with new + ordered_keys.pop(v["order"]) + except IndexError: + pass + # add key into correct order + ordered_keys.insert(v["order"], k) + + # process ordered + for k in ordered_keys: + v = data[k] + tool_tip = v.get("toolTip", "") + if v["type"] == "dict": + # adding spacer between sections + self.content_layout.append(QtWidgets.QWidget(self)) + content_layout.addWidget(self.content_layout[-1]) + self.content_layout[-1].setObjectName("sectionHeadline") + + headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) + headline.addWidget(Spacer(20, self)) + headline.addWidget(QtWidgets.QLabel(v["label"])) + + # adding nested layout with label + self.content_layout.append(QtWidgets.QWidget(self)) + self.content_layout[-1].setObjectName("sectionContent") + + nested_content_layout = QtWidgets.QFormLayout( + self.content_layout[-1]) + nested_content_layout.setObjectName("NestedContentLayout") + content_layout.addWidget(self.content_layout[-1]) + + # add nested key as label + data[k]["value"] = self.populate_widgets( + v["value"], nested_content_layout) + + if v["type"] == "section": + # adding spacer between sections + self.content_layout.append(QtWidgets.QWidget(self)) + content_layout.addWidget(self.content_layout[-1]) + self.content_layout[-1].setObjectName("sectionHeadline") + + headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) + headline.addWidget(Spacer(20, self)) + headline.addWidget(QtWidgets.QLabel(v["label"])) + + # adding nested layout with label + self.content_layout.append(QtWidgets.QWidget(self)) + self.content_layout[-1].setObjectName("sectionContent") + + nested_content_layout = QtWidgets.QFormLayout( + self.content_layout[-1]) + nested_content_layout.setObjectName("NestedContentLayout") + content_layout.addWidget(self.content_layout[-1]) + + # add nested key as label + data[k]["value"] = self.populate_widgets( + v["value"], nested_content_layout) + + elif v["type"] == "QLineEdit": + data[k]["value"] = self.create_row( + content_layout, "QLineEdit", v["label"], + setText=v["value"], setToolTip=tool_tip) + elif v["type"] == "QComboBox": + data[k]["value"] = self.create_row( + content_layout, "QComboBox", v["label"], + addItems=v["value"], setToolTip=tool_tip) + elif v["type"] == "QCheckBox": + data[k]["value"] = self.create_row( + content_layout, "QCheckBox", v["label"], + setChecked=v["value"], setToolTip=tool_tip) + elif v["type"] == "QSpinBox": + data[k]["value"] = self.create_row( + content_layout, "QSpinBox", v["label"], + setValue=v["value"], setMinimum=0, + setMaximum=100000, setToolTip=tool_tip) + 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) + + self.setLayout(layout) + + +class Creator(openpype.Creator): + """Creator class wrapper + """ + clip_color = lib.ctx.color_map["purple"] + rename_index = None + + def __init__(self, *args, **kwargs): + super(Creator, self).__init__(*args, **kwargs) + self.presets = openpype.get_current_project_settings()[ + "flame"]["create"].get(self.__class__.__name__, {}) + + # adding basic current context flame objects + self.project = lib.get_current_project() + self.sequence = lib.get_current_sequence(opflame.selection) + + if (self.options or {}).get("useSelection"): + self.selected = lib.get_sequence_segments(self.sequence, True) + else: + self.selected = lib.get_sequence_segments(self.sequence) + + self.widget = CreatorWidget + + +class PublishableClip: + """ + Convert a segment to publishable instance + + Args: + segment (flame.PySegment): flame api object + kwargs (optional): additional data needed for rename=True (presets) + + Returns: + flame.PySegment: flame api object + """ + vertical_clip_match = dict() + tag_data = dict() + types = { + "shot": "shot", + "folder": "folder", + "episode": "episode", + "sequence": "sequence", + "track": "sequence", + } + + # parents search patern + parents_search_patern = r"\{([a-z]*?)\}" + + # default templates for non-ui use + rename_default = False + hierarchy_default = "{_folder_}/{_sequence_}/{_track_}" + clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}" + subset_name_default = "[ track name ]" + review_track_default = "[ none ]" + subset_family_default = "plate" + count_from_default = 10 + count_steps_default = 10 + vertical_sync_default = False + driving_layer_default = "" + + def __init__(self, cls, segment, **kwargs): + # populate input cls attribute onto self.[attr] + self.__dict__.update(cls.__dict__) + + # get main parent objects + self.current_segment = segment + sequence_name = lib.get_current_sequence([segment]).name.get_value() + self.sequence_name = str(sequence_name).replace(" ", "_") + + self.clip_data = lib.get_segment_attributes(segment) + # segment (clip) main attributes + self.cs_name = self.clip_data["segment_name"] + self.cs_index = int(self.clip_data["segment"]) + + # get track name and index + self.track_index = int(self.clip_data["track"]) + track_name = self.clip_data["track_name"] + self.track_name = str(track_name).replace(" ", "_").replace( + "*", "noname{}".format(self.track_index)) + + # adding tag.family into tag + if kwargs.get("avalon"): + self.tag_data.update(kwargs["avalon"]) + + # add publish attribute to marker data + self.tag_data.update({"publish": True}) + + # adding ui inputs if any + self.ui_inputs = kwargs.get("ui_inputs", {}) + + # populate default data before we get other attributes + self._populate_segment_default_data() + + # use all populated default data to create all important attributes + self._populate_attributes() + + # create parents with correct types + self._create_parents() + + def convert(self): + + # solve segment data and add them to marker data + self._convert_to_marker_data() + + # if track name is in review track name and also if driving track name + # is not in review track name: skip tag creation + if (self.track_name in self.review_layer) and ( + self.driving_layer not in self.review_layer): + return + + # deal with clip name + new_name = self.tag_data.pop("newClipName") + + if self.rename: + # rename segment + self.current_segment.setName(new_name) + self.tag_data["asset"] = new_name + else: + self.tag_data["asset"] = self.cs_name + self.tag_data["hierarchyData"]["shot"] = self.cs_name + + if self.tag_data["heroTrack"] and self.review_layer: + self.tag_data.update({"reviewTrack": self.review_layer}) + else: + self.tag_data.update({"reviewTrack": None}) + + # create pype tag on track_item and add data + lib.imprint(self.current_segment, self.tag_data) + + return self.current_segment + + def _populate_segment_default_data(self): + """ Populate default formating data from segment. """ + + self.current_segment_default_data = { + "_folder_": "shots", + "_sequence_": self.sequence_name, + "_track_": self.track_name, + "_clip_": self.cs_name, + "_trackIndex_": self.track_index, + "_clipIndex_": self.cs_index + } + + def _populate_attributes(self): + """ Populate main object attributes. """ + # segment frame range and parent track name for vertical sync check + self.clip_in = int(self.clip_data["record_in"]) + self.clip_out = int(self.clip_data["record_out"]) + + # define ui inputs if non gui mode was used + self.shot_num = self.cs_index + log.debug( + "____ self.shot_num: {}".format(self.shot_num)) + + # ui_inputs data or default values if gui was not used + self.rename = self.ui_inputs.get( + "clipRename", {}).get("value") or self.rename_default + self.clip_name = self.ui_inputs.get( + "clipName", {}).get("value") or self.clip_name_default + self.hierarchy = self.ui_inputs.get( + "hierarchy", {}).get("value") or self.hierarchy_default + self.hierarchy_data = self.ui_inputs.get( + "hierarchyData", {}).get("value") or \ + self.current_segment_default_data.copy() + self.count_from = self.ui_inputs.get( + "countFrom", {}).get("value") or self.count_from_default + self.count_steps = self.ui_inputs.get( + "countSteps", {}).get("value") or self.count_steps_default + self.subset_name = self.ui_inputs.get( + "subsetName", {}).get("value") or self.subset_name_default + self.subset_family = self.ui_inputs.get( + "subsetFamily", {}).get("value") or self.subset_family_default + self.vertical_sync = self.ui_inputs.get( + "vSyncOn", {}).get("value") or self.vertical_sync_default + self.driving_layer = self.ui_inputs.get( + "vSyncTrack", {}).get("value") or self.driving_layer_default + self.review_track = self.ui_inputs.get( + "reviewTrack", {}).get("value") or self.review_track_default + self.audio = self.ui_inputs.get( + "audio", {}).get("value") or False + + # build subset name from layer name + if self.subset_name == "[ track name ]": + self.subset_name = self.track_name + + # create subset for publishing + self.subset = self.subset_family + self.subset_name.capitalize() + + def _replace_hash_to_expression(self, name, text): + """ Replace hash with number in correct padding. """ + _spl = text.split("#") + _len = (len(_spl) - 1) + _repl = "{{{0}:0>{1}}}".format(name, _len) + return text.replace(("#" * _len), _repl) + + + def _convert_to_marker_data(self): + """ Convert internal data to marker data. + + Populating the marker data into internal variable self.tag_data + """ + # define vertical sync attributes + hero_track = True + self.review_layer = "" + if self.vertical_sync and self.track_name not in self.driving_layer: + # if it is not then define vertical sync as None + hero_track = False + + # increasing steps by index of rename iteration + self.count_steps *= self.rename_index + + hierarchy_formating_data = {} + hierarchy_data = deepcopy(self.hierarchy_data) + _data = self.current_segment_default_data.copy() + if self.ui_inputs: + # adding tag metadata from ui + for _k, _v in self.ui_inputs.items(): + if _v["target"] == "tag": + self.tag_data[_k] = _v["value"] + + # driving layer is set as positive match + if hero_track or self.vertical_sync: + # mark review layer + if self.review_track and ( + self.review_track not in self.review_track_default): + # if review layer is defined and not the same as defalut + self.review_layer = self.review_track + # shot num calculate + if self.rename_index == 0: + self.shot_num = self.count_from + else: + self.shot_num = self.count_from + self.count_steps + + # clip name sequence number + _data.update({"shot": self.shot_num}) + + # solve # in test to pythonic expression + for _k, _v in hierarchy_data.items(): + if "#" not in _v["value"]: + continue + hierarchy_data[ + _k]["value"] = self._replace_hash_to_expression( + _k, _v["value"]) + + # fill up pythonic expresisons in hierarchy data + for k, _v in hierarchy_data.items(): + hierarchy_formating_data[k] = _v["value"].format(**_data) + else: + # if no gui mode then just pass default data + hierarchy_formating_data = hierarchy_data + + tag_hierarchy_data = self._solve_tag_hierarchy_data( + hierarchy_formating_data + ) + + tag_hierarchy_data.update({"heroTrack": True}) + if hero_track and self.vertical_sync: + self.vertical_clip_match.update({ + (self.clip_in, self.clip_out): tag_hierarchy_data + }) + + if not hero_track and self.vertical_sync: + # driving layer is set as negative match + for (_in, _out), hero_data in self.vertical_clip_match.items(): + hero_data.update({"heroTrack": False}) + if _in == self.clip_in and _out == self.clip_out: + data_subset = hero_data["subset"] + # add track index in case duplicity of names in hero data + if self.subset in data_subset: + hero_data["subset"] = self.subset + str( + self.track_index) + # in case track name and subset name is the same then add + if self.subset_name == self.track_name: + hero_data["subset"] = self.subset + # assing data to return hierarchy data to tag + tag_hierarchy_data = hero_data + + # add data to return data dict + self.tag_data.update(tag_hierarchy_data) + + def _solve_tag_hierarchy_data(self, hierarchy_formating_data): + """ Solve marker data from hierarchy data and templates. """ + # fill up clip name and hierarchy keys + hierarchy_filled = self.hierarchy.format(**hierarchy_formating_data) + clip_name_filled = self.clip_name.format(**hierarchy_formating_data) + + # remove shot from hierarchy data: is not needed anymore + hierarchy_formating_data.pop("shot") + + return { + "newClipName": clip_name_filled, + "hierarchy": hierarchy_filled, + "parents": self.parents, + "hierarchyData": hierarchy_formating_data, + "subset": self.subset, + "family": self.subset_family, + "families": [self.data["family"]] + } + + def _convert_to_entity(self, type, template): + """ Converting input key to key with type. """ + # convert to entity type + entity_type = self.types.get(type, None) + + assert entity_type, "Missing entity type for `{}`".format( + type + ) + + # first collect formating data to use for formating template + formating_data = {} + for _k, _v in self.hierarchy_data.items(): + value = _v["value"].format( + **self.current_segment_default_data) + formating_data[_k] = value + + return { + "entity_type": entity_type, + "entity_name": template.format( + **formating_data + ) + } + + def _create_parents(self): + """ Create parents and return it in list. """ + self.parents = [] + + patern = re.compile(self.parents_search_patern) + + par_split = [(patern.findall(t).pop(), t) + for t in self.hierarchy.split("/")] + + for type, template in par_split: + parent = self._convert_to_entity(type, template) + self.parents.append(parent) + + # Publishing plugin functions # Loader plugin functions diff --git a/openpype/hosts/flame/api/style.css b/openpype/hosts/flame/api/style.css new file mode 100644 index 0000000000..b64c391c6e --- /dev/null +++ b/openpype/hosts/flame/api/style.css @@ -0,0 +1,26 @@ +QWidget { + font-size: 13px; +} + +QSpinBox { + padding: 2; + max-width: 8em; +} + +QLineEdit { + padding: 2; + min-width: 15em; +} + +QVBoxLayout { + min-width: 15em; + background-color: #201f1f; +} + +QComboBox { + min-width: 8em; +} + +#sectionContent { + background-color: #2E2D2D; +} \ No newline at end of file From 25e0bffe5854cbfbcfd43d8e0001c8d4471e2ee3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 14:04:01 +0100 Subject: [PATCH 12/51] flame: adding create plugin for publishable clips --- .../flame/plugins/create/create_shot_clip.py | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 openpype/hosts/flame/plugins/create/create_shot_clip.py diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py new file mode 100644 index 0000000000..71ea9b3c86 --- /dev/null +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -0,0 +1,268 @@ +from copy import deepcopy +import openpype.hosts.flame as opflame +import openpype.hosts.flame.api.plugin as fplugin +import openpype.hosts.flame.api.lib as flib +reload(fplugin) +reload(flib) + +def _get_video_track_names(sequence): + track_names = [] + for ver in sequence.versions: + for track in ver.tracks: + track_names.append(track.name.get_value()) + +class CreateShotClip(fplugin.Creator): + """Publishable clip""" + + label = "Create Publishable Clip" + family = "clip" + icon = "film" + defaults = ["Main"] + + gui_tracks = _get_video_track_names( + flib.get_current_sequence(opflame.selection) + ) + gui_name = "Pype publish attributes creator" + gui_info = "Define sequential rename and fill hierarchy data." + gui_inputs = { + "renameHierarchy": { + "type": "section", + "label": "Shot Hierarchy And Rename Settings", + "target": "ui", + "order": 0, + "value": { + "hierarchy": { + "value": "{folder}/{sequence}", + "type": "QLineEdit", + "label": "Shot Parent Hierarchy", + "target": "tag", + "toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa + "order": 0}, + "clipRename": { + "value": False, + "type": "QCheckBox", + "label": "Rename clips", + "target": "ui", + "toolTip": "Renaming selected clips on fly", # noqa + "order": 1}, + "clipName": { + "value": "{sequence}{shot}", + "type": "QLineEdit", + "label": "Clip Name Template", + "target": "ui", + "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa + "order": 2}, + "countFrom": { + "value": 10, + "type": "QSpinBox", + "label": "Count sequence from", + "target": "ui", + "toolTip": "Set when the sequence number stafrom", # noqa + "order": 3}, + "countSteps": { + "value": 10, + "type": "QSpinBox", + "label": "Stepping number", + "target": "ui", + "toolTip": "What number is adding every new step", # noqa + "order": 4}, + } + }, + "hierarchyData": { + "type": "dict", + "label": "Shot Template Keywords", + "target": "tag", + "order": 1, + "value": { + "folder": { + "value": "shots", + "type": "QLineEdit", + "label": "{folder}", + "target": "tag", + "toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 0}, + "episode": { + "value": "ep01", + "type": "QLineEdit", + "label": "{episode}", + "target": "tag", + "toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 1}, + "sequence": { + "value": "sq01", + "type": "QLineEdit", + "label": "{sequence}", + "target": "tag", + "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 2}, + "track": { + "value": "{_track_}", + "type": "QLineEdit", + "label": "{track}", + "target": "tag", + "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 3}, + "shot": { + "value": "sh###", + "type": "QLineEdit", + "label": "{shot}", + "target": "tag", + "toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 4} + } + }, + "verticalSync": { + "type": "section", + "label": "Vertical Synchronization Of Attributes", + "target": "ui", + "order": 2, + "value": { + "vSyncOn": { + "value": True, + "type": "QCheckBox", + "label": "Enable Vertical Sync", + "target": "ui", + "toolTip": "Switch on if you want clips above each other to share its attributes", # noqa + "order": 0}, + "vSyncTrack": { + "value": gui_tracks, # noqa + "type": "QComboBox", + "label": "Hero track", + "target": "ui", + "toolTip": "Select driving track name which should be hero for all others", # noqa + "order": 1} + } + }, + "publishSettings": { + "type": "section", + "label": "Publish Settings", + "target": "ui", + "order": 3, + "value": { + "subsetName": { + "value": ["", "main", "bg", "fg", "bg", + "animatic"], + "type": "QComboBox", + "label": "Subset Name", + "target": "ui", + "toolTip": "chose subset name patern, if is selected, name of track layer will be used", # noqa + "order": 0}, + "subsetFamily": { + "value": ["plate", "take"], + "type": "QComboBox", + "label": "Subset Family", + "target": "ui", "toolTip": "What use of this subset is for", # noqa + "order": 1}, + "reviewTrack": { + "value": ["< none >"] + gui_tracks, + "type": "QComboBox", + "label": "Use Review Track", + "target": "ui", + "toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa + "order": 2}, + "audio": { + "value": False, + "type": "QCheckBox", + "label": "Include audio", + "target": "tag", + "toolTip": "Process subsets with corresponding audio", # noqa + "order": 3}, + "sourceResolution": { + "value": False, + "type": "QCheckBox", + "label": "Source resolution", + "target": "tag", + "toolTip": "Is resloution taken from timeline or source?", # noqa + "order": 4}, + } + }, + "frameRangeAttr": { + "type": "section", + "label": "Shot Attributes", + "target": "ui", + "order": 4, + "value": { + "workfileFrameStart": { + "value": 1001, + "type": "QSpinBox", + "label": "Workfiles Start Frame", + "target": "tag", + "toolTip": "Set workfile starting frame number", # noqa + "order": 0 + }, + "handleStart": { + "value": 0, + "type": "QSpinBox", + "label": "Handle Start", + "target": "tag", + "toolTip": "Handle at start of clip", # noqa + "order": 1 + }, + "handleEnd": { + "value": 0, + "type": "QSpinBox", + "label": "Handle End", + "target": "tag", + "toolTip": "Handle at end of clip", # noqa + "order": 2 + } + } + } + } + + presets = None + + def process(self): + # Creator copy of object attributes that are modified during `process` + presets = deepcopy(self.presets) + gui_inputs = deepcopy(self.gui_inputs) + + # get key pares from presets and match it on ui inputs + for k, v in gui_inputs.items(): + if v["type"] in ("dict", "section"): + # nested dictionary (only one level allowed + # for sections and dict) + for _k, _v in v["value"].items(): + if presets.get(_k): + gui_inputs[k][ + "value"][_k]["value"] = presets[_k] + if presets.get(k): + gui_inputs[k]["value"] = presets[k] + + # open widget for plugins inputs + widget = self.widget(self.gui_name, self.gui_info, gui_inputs) + widget.exec_() + + if len(self.selected) < 1: + return + + if not widget.result: + print("Operation aborted") + return + + self.rename_add = 0 + + # get ui output for track name for vertical sync + v_sync_track = widget.result["vSyncTrack"]["value"] + + # sort selected trackItems by + sorted_selected_segments = [] + unsorted_selected_segments = [] + for _segment in self.selected: + if _segment.parent.name.get_value() in v_sync_track: + sorted_selected_segments.append(_segment) + else: + unsorted_selected_segments.append(_segment) + + sorted_selected_segments.extend(unsorted_selected_segments) + + kwargs = { + "ui_inputs": widget.result, + "avalon": self.data + } + + for i, segment in enumerate(sorted_selected_segments): + self.rename_index = i + + # convert track item to timeline media pool item + fplugin.PublishableClip(self, segment, **kwargs).convert() From e2ab00c54a91ffbf3d07c37b26d8a9f06e487f58 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 14:17:32 +0100 Subject: [PATCH 13/51] flame: tuning creator --- openpype/hosts/flame/api/plugin.py | 35 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 1a3880a19a..34e626b099 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -3,7 +3,7 @@ import os from Qt import QtWidgets, QtCore import openpype.api as openpype import openpype.hosts.flame as opflame -from . import lib +from . import lib, pipeline from copy import deepcopy log = openpype.Logger().get_logger(__name__) @@ -321,8 +321,8 @@ class PublishableClip: Returns: flame.PySegment: flame api object """ - vertical_clip_match = dict() - tag_data = dict() + vertical_clip_match = {} + marker_data = {} types = { "shot": "shot", "folder": "folder", @@ -368,10 +368,10 @@ class PublishableClip: # adding tag.family into tag if kwargs.get("avalon"): - self.tag_data.update(kwargs["avalon"]) + self.marker_data.update(kwargs["avalon"]) # add publish attribute to marker data - self.tag_data.update({"publish": True}) + self.marker_data.update({"publish": True}) # adding ui inputs if any self.ui_inputs = kwargs.get("ui_inputs", {}) @@ -397,23 +397,23 @@ class PublishableClip: return # deal with clip name - new_name = self.tag_data.pop("newClipName") + new_name = self.marker_data.pop("newClipName") if self.rename: # rename segment - self.current_segment.setName(new_name) - self.tag_data["asset"] = new_name + self.current_segment.name = new_name + self.marker_data["asset"] = new_name else: - self.tag_data["asset"] = self.cs_name - self.tag_data["hierarchyData"]["shot"] = self.cs_name + self.marker_data["asset"] = self.cs_name + self.marker_data["hierarchyData"]["shot"] = self.cs_name - if self.tag_data["heroTrack"] and self.review_layer: - self.tag_data.update({"reviewTrack": self.review_layer}) + if self.marker_data["heroTrack"] and self.review_layer: + self.marker_data.update({"reviewTrack": self.review_layer}) else: - self.tag_data.update({"reviewTrack": None}) + self.marker_data.update({"reviewTrack": None}) # create pype tag on track_item and add data - lib.imprint(self.current_segment, self.tag_data) + pipeline.imprint(self.current_segment, self.marker_data) return self.current_segment @@ -481,11 +481,10 @@ class PublishableClip: _repl = "{{{0}:0>{1}}}".format(name, _len) return text.replace(("#" * _len), _repl) - def _convert_to_marker_data(self): """ Convert internal data to marker data. - Populating the marker data into internal variable self.tag_data + Populating the marker data into internal variable self.marker_data """ # define vertical sync attributes hero_track = True @@ -504,7 +503,7 @@ class PublishableClip: # adding tag metadata from ui for _k, _v in self.ui_inputs.items(): if _v["target"] == "tag": - self.tag_data[_k] = _v["value"] + self.marker_data[_k] = _v["value"] # driving layer is set as positive match if hero_track or self.vertical_sync: @@ -564,7 +563,7 @@ class PublishableClip: tag_hierarchy_data = hero_data # add data to return data dict - self.tag_data.update(tag_hierarchy_data) + self.marker_data.update(tag_hierarchy_data) def _solve_tag_hierarchy_data(self, hierarchy_formating_data): """ Solve marker data from hierarchy data and templates. """ From a2bb8f1839ca253daa962a876719e94262115f6e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 14:59:59 +0100 Subject: [PATCH 14/51] flame: beautification of code --- openpype/hosts/flame/api/pipeline.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 2295589627..99a33e4cb9 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -22,7 +22,6 @@ def install(): CREATE_PATH, INVENTORY_PATH ) - # TODO: install # Disable all families except for the ones we explicitly want to see family_states = [ @@ -36,19 +35,18 @@ def install(): avalon.data["familiesStateDefault"] = False avalon.data["familiesStateToggled"] = family_states - log.info("openpype.hosts.flame installed") pyblish.register_host("flame") pyblish.register_plugin_path(PUBLISH_PATH) - log.info("Registering Flame plug-ins..") - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + log.info("OpenPype Flame plug-ins registred ...") # register callback for switching publishable pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + log.info("OpenPype Flame host installed ...") def uninstall(): from .. import ( @@ -58,11 +56,10 @@ def uninstall(): INVENTORY_PATH ) - # TODO: uninstall pyblish.deregister_host("flame") - pyblish.deregister_plugin_path(PUBLISH_PATH) - log.info("Deregistering DaVinci Resovle plug-ins..") + log.info("Deregistering Flame plug-ins..") + pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) @@ -70,6 +67,8 @@ def uninstall(): # register callback for switching publishable pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) + log.info("OpenPype Flame host uninstalled ...") + def containerise(tl_segment, name, From 2f0fcaebe150b307e598b070bd996568eec1c2ac Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 15:00:15 +0100 Subject: [PATCH 15/51] flame: fix missing return --- openpype/hosts/flame/plugins/create/create_shot_clip.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 71ea9b3c86..79afca507f 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -11,6 +11,8 @@ def _get_video_track_names(sequence): for track in ver.tracks: track_names.append(track.name.get_value()) + return track_names + class CreateShotClip(fplugin.Creator): """Publishable clip""" From 5c11089aff32e09c17439743290b50a4097c8714 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 15:00:37 +0100 Subject: [PATCH 16/51] flame: testing plugin refactory --- .../plugins/publish/collect_test_selection.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index d30d6ed331..cd7355d1f5 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -1,9 +1,11 @@ +import os import pyblish.api import openpype.hosts.flame as opflame from openpype.hosts.flame.otio import flame_export as otio_export -from openpype.hosts.flame.api import lib +from openpype.hosts.flame.api import lib, pipeline from pprint import pformat reload(lib) # noqa +reload(pipeline) # noqa reload(otio_export) # noqa @@ -17,14 +19,30 @@ class CollectTestSelection(pyblish.api.ContextPlugin): hosts = ["flame"] def process(self, context): - self.log.info(opflame.selection) + self.log.info( + "Active Selection: {}".format(opflame.selection)) sequence = lib.get_current_sequence(opflame.selection) + self.test_imprint_data(sequence) + self.test_otio_export(sequence) + + def test_otio_export(self, sequence): + home_dir = os.path.expanduser("~") + export_path = os.path.normalize( + os.path.join( + home_dir, "otio_timeline_export.otio" + ) + ) otio_timeline = otio_export.create_otio_timeline(sequence) + otio_export.write_to_file( + otio_timeline, export_path + ) self.log.info(pformat(otio_timeline)) + self.log.info("Otio exported to: {}".format(export_path)) + def test_imprint_data(self, sequence): # test segment markers for ver in sequence.versions: for track in ver.tracks: @@ -40,8 +58,8 @@ class CollectTestSelection(pyblish.api.ContextPlugin): self.log.debug("Segment with OpenPypeData: {}".format( segment.name)) - lib.imprint(segment, { - 'asset': 'sq020sh0280', + pipeline.imprint(segment, { + 'asset': segment.name.get_value(), 'family': 'render', 'subset': 'subsetMain' }) From c67e672acaaa0ed5a459ef0e52a1cd81b6ee049d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 15:08:06 +0100 Subject: [PATCH 17/51] flame: improving testing plugin adding maintained seqment selection functionality --- openpype/hosts/flame/api/lib.py | 2 +- .../plugins/publish/collect_test_selection.py | 27 +++++++------------ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 2d30390d21..e5642dd6f9 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -14,7 +14,7 @@ class ctx: # OpenPype marker workflow variables marker_name = "OpenPypeData" marker_duration = 0 - marker_color = "red" + marker_color = "cyan" publish_default = False color_map = { "red": (1.0, 0.0, 0.0), diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index cd7355d1f5..875ef34a07 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -43,23 +43,16 @@ class CollectTestSelection(pyblish.api.ContextPlugin): self.log.info("Otio exported to: {}".format(export_path)) def test_imprint_data(self, sequence): - # test segment markers - for ver in sequence.versions: - for track in ver.tracks: - if len(track.segments) == 0 and track.hidden: + with lib.maintained_segment_selection(sequence) as selected_segments: + for segment in selected_segments: + if str(segment.name)[1:-1] == "": continue - for segment in track.segments: - if str(segment.name)[1:-1] == "": - continue - if not segment.selected: - continue + self.log.debug("Segment with OpenPypeData: {}".format( + segment.name)) - self.log.debug("Segment with OpenPypeData: {}".format( - segment.name)) - - pipeline.imprint(segment, { - 'asset': segment.name.get_value(), - 'family': 'render', - 'subset': 'subsetMain' - }) + pipeline.imprint(segment, { + 'asset': segment.name.get_value(), + 'family': 'render', + 'subset': 'subsetMain' + }) From 12f9eb2c328259c105cfe036cee08a14016be0a5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 15:09:41 +0100 Subject: [PATCH 18/51] flame: fix normalize to normpath --- openpype/hosts/flame/plugins/publish/collect_test_selection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 875ef34a07..29ca08d9b5 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -29,7 +29,7 @@ class CollectTestSelection(pyblish.api.ContextPlugin): def test_otio_export(self, sequence): home_dir = os.path.expanduser("~") - export_path = os.path.normalize( + export_path = os.path.normpath( os.path.join( home_dir, "otio_timeline_export.otio" ) From 02c41986250d1cc05dada82e4e32afc3e238536d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 15:20:58 +0100 Subject: [PATCH 19/51] flame: settings for creator plugin --- .../defaults/project_settings/flame.json | 20 +++ .../schemas/projects_schema/schema_main.json | 4 + .../projects_schema/schema_project_flame.json | 124 ++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 openpype/settings/defaults/project_settings/flame.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_flame.json diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json new file mode 100644 index 0000000000..b6fbdecc95 --- /dev/null +++ b/openpype/settings/defaults/project_settings/flame.json @@ -0,0 +1,20 @@ +{ + "create": { + "CreateShotClip": { + "hierarchy": "{folder}/{sequence}", + "clipRename": true, + "clipName": "{track}{sequence}{shot}", + "countFrom": 10, + "countSteps": 10, + "folder": "shots", + "episode": "ep01", + "sequence": "sq01", + "track": "{_track_}", + "shot": "sh###", + "vSyncOn": false, + "workfileFrameStart": 1001, + "handleStart": 10, + "handleEnd": 10 + } + } +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index c9eca5dedd..8a2ad451ee 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -110,6 +110,10 @@ "type": "schema", "name": "schema_project_celaction" }, + { + "type": "schema", + "name": "schema_project_flame" + }, { "type": "schema", "name": "schema_project_resolve" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json new file mode 100644 index 0000000000..d713c37620 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -0,0 +1,124 @@ +{ + "type": "dict", + "collapsible": true, + "key": "flame", + "label": "Flame", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "create", + "label": "Create plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "CreateShotClip", + "label": "Create Shot Clip", + "is_group": true, + "children": [ + { + "type": "collapsible-wrap", + "label": "Shot Hierarchy And Rename Settings", + "collapsible": false, + "children": [ + { + "type": "text", + "key": "hierarchy", + "label": "Shot parent hierarchy" + }, + { + "type": "boolean", + "key": "clipRename", + "label": "Rename clips" + }, + { + "type": "text", + "key": "clipName", + "label": "Clip name template" + }, + { + "type": "number", + "key": "countFrom", + "label": "Count sequence from" + }, + { + "type": "number", + "key": "countSteps", + "label": "Stepping number" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Shot Template Keywords", + "collapsible": false, + "children": [ + { + "type": "text", + "key": "folder", + "label": "{folder}" + }, + { + "type": "text", + "key": "episode", + "label": "{episode}" + }, + { + "type": "text", + "key": "sequence", + "label": "{sequence}" + }, + { + "type": "text", + "key": "track", + "label": "{track}" + }, + { + "type": "text", + "key": "shot", + "label": "{shot}" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Vertical Synchronization Of Attributes", + "collapsible": false, + "children": [ + { + "type": "boolean", + "key": "vSyncOn", + "label": "Enable Vertical Sync" + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Shot Attributes", + "collapsible": false, + "children": [ + { + "type": "number", + "key": "workfileFrameStart", + "label": "Workfiles Start Frame" + }, + { + "type": "number", + "key": "handleStart", + "label": "Handle start (head)" + }, + { + "type": "number", + "key": "handleEnd", + "label": "Handle end (tail)" + } + ] + } + ] + } + ] + } + ] +} From 3aa0efdc4dacee60e7995a4d32bfd675c0ae0afd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 15:29:59 +0100 Subject: [PATCH 20/51] flame: do not alter project attributes if it exists already --- openpype/hosts/flame/api/scripts/wiretap_com.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/api/scripts/wiretap_com.py b/openpype/hosts/flame/api/scripts/wiretap_com.py index 5f7b2580e6..f1b5ab2236 100644 --- a/openpype/hosts/flame/api/scripts/wiretap_com.py +++ b/openpype/hosts/flame/api/scripts/wiretap_com.py @@ -89,9 +89,11 @@ class WireTapCom(object): workspace_name = kwargs.get("workspace_name") color_policy = kwargs.get("color_policy") - self._project_prep(project_name) - self._set_project_settings(project_name, project_data) - self._set_project_colorspace(project_name, color_policy) + project_exists = self._project_prep(project_name) + if not project_exists: + self._set_project_settings(project_name, project_data) + self._set_project_colorspace(project_name, color_policy) + user_name = self._user_prep(user_name) if workspace_name is None: @@ -207,6 +209,7 @@ class WireTapCom(object): print( "A new project '{}' is created.".format(project_name)) + return project_exists def _get_all_volumes(self): """Request all available volumens from WireTap From f6b9d122442d8cd95b4b9307a7d2cad4466fdddd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 15:57:45 +0100 Subject: [PATCH 21/51] flame: host maintained selection --- openpype/hosts/flame/__init__.py | 4 +++- openpype/hosts/flame/api/pipeline.py | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index 691b6f8119..da42b313aa 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -10,7 +10,8 @@ from .api.pipeline import ( update_container, remove_instance, list_instances, - imprint + imprint, + maintained_selection ) from .api.lib import ( @@ -87,6 +88,7 @@ __all__ = [ "remove_instance", "list_instances", "imprint", + "maintained_selection", # utils "setup", diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 99a33e4cb9..ee0e12584a 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -7,7 +7,9 @@ from pyblish import api as pyblish from openpype.api import Logger from .lib import ( set_segment_data_marker, - set_publish_attribute + set_publish_attribute, + maintained_segment_selection, + get_current_sequence ) AVALON_CONTAINERS = "AVALON_CONTAINERS" @@ -151,3 +153,17 @@ def imprint(segment, data=None): # add publish attribute set_publish_attribute(segment, True) + +@contextlib.contextmanager +def maintained_selection(): + import flame + from .. import selection + + # check if segment is selected + if isinstance(selection[0], flame.PySegment): + sequence = get_current_sequence(selection) + try: + with maintained_segment_selection(sequence): + yield + finally: + pass \ No newline at end of file From b3082d9e211aa2245262654d52eb0f89758db04a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 16:08:57 +0100 Subject: [PATCH 22/51] flame: track name preset fix --- openpype/hosts/flame/plugins/create/create_shot_clip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 79afca507f..45c4557dad 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -142,12 +142,12 @@ class CreateShotClip(fplugin.Creator): "order": 3, "value": { "subsetName": { - "value": ["", "main", "bg", "fg", "bg", + "value": ["[ track name ]", "main", "bg", "fg", "bg", "animatic"], "type": "QComboBox", "label": "Subset Name", "target": "ui", - "toolTip": "chose subset name patern, if is selected, name of track layer will be used", # noqa + "toolTip": "chose subset name patern, if [ track name ] is selected, name of track layer will be used", # noqa "order": 0}, "subsetFamily": { "value": ["plate", "take"], From f48b864e2359398c960debd2ab0a9668792ca00c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 16:12:38 +0100 Subject: [PATCH 23/51] Flame: fixing wrong type --- openpype/hosts/flame/api/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 34e626b099..5e47ce3a68 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -401,8 +401,8 @@ class PublishableClip: if self.rename: # rename segment - self.current_segment.name = new_name - self.marker_data["asset"] = new_name + self.current_segment.name = str(new_name) + self.marker_data["asset"] = str(new_name) else: self.marker_data["asset"] = self.cs_name self.marker_data["hierarchyData"]["shot"] = self.cs_name From ae74e0a79569308fa6dc6e602dc0b6d0d3e3c323 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 16:37:02 +0100 Subject: [PATCH 24/51] flame: stylize creator gui --- openpype/hosts/flame/api/style.css | 51 +++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/style.css b/openpype/hosts/flame/api/style.css index b64c391c6e..21e4750513 100644 --- a/openpype/hosts/flame/api/style.css +++ b/openpype/hosts/flame/api/style.css @@ -1,5 +1,5 @@ QWidget { - font-size: 13px; + font: 14px "Discreet"; } QSpinBox { @@ -8,13 +8,46 @@ QSpinBox { } QLineEdit { - padding: 2; - min-width: 15em; + color: #9a9a9a; + background-color: #373e47; + selection-color: #262626; + selection-background-color: #b8b1a7; + font: 14px "Discreet" +} +QLineEdit:focus { + background-color: #474e58 +} + +QLineEdit:disabled { + color: #6a6a6a; + background-color: #373737 +} + +QPushButton { + color: #9a9a9a; + background-color: #424142; + border-top: 1px inset #555555; + border-bottom: 1px inset black; + font: 14px "Discreet" +} + +QPushButton:pressed { + color: #d9d9d9; + background-color: #4f4f4f; + border-top: 1px inset #666666; + font: italic +} + +QPushButton:disabled { + color: #747474; + background-color: #353535; + border-top: 1px solid #444444; + border-bottom: 1px solid #242424 } QVBoxLayout { min-width: 15em; - background-color: #201f1f; + background-color: #313131 } QComboBox { @@ -23,4 +56,14 @@ QComboBox { #sectionContent { background-color: #2E2D2D; +} + +QLabel { + color: #9a9a9a; + border-bottom: 1px inset #282828; + font: 14px "Discreet" +} + +QLabel:disabled { + color: #6a6a6a } \ No newline at end of file From 09214e175b9674fb465517eaf5e4092701e09f6d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 16:59:02 +0100 Subject: [PATCH 25/51] Flame: adding openpype style to creator gui --- openpype/hosts/flame/api/plugin.py | 15 +------ openpype/hosts/flame/api/style.css | 69 ------------------------------ 2 files changed, 2 insertions(+), 82 deletions(-) delete mode 100644 openpype/hosts/flame/api/style.css diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 5e47ce3a68..b4fc75fe00 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -2,23 +2,13 @@ import re import os from Qt import QtWidgets, QtCore import openpype.api as openpype +from openpype import style import openpype.hosts.flame as opflame from . import lib, pipeline from copy import deepcopy log = openpype.Logger().get_logger(__name__) -# Creator plugin functions -def load_stylesheet(): - path = os.path.join(os.path.dirname(__file__), "style.css") - if not os.path.exists(path): - log.warning("Unable to load stylesheet, file not found in resources") - return "" - - with open(path, "r") as file_stream: - stylesheet = file_stream.read() - return stylesheet - class CreatorWidget(QtWidgets.QDialog): @@ -93,8 +83,7 @@ class CreatorWidget(QtWidgets.QDialog): ok_btn.clicked.connect(self._on_ok_clicked) cancel_btn.clicked.connect(self._on_cancel_clicked) - stylesheet = load_stylesheet() - self.setStyleSheet(stylesheet) + self.setStyleSheet(style.load_stylesheet()) def _on_ok_clicked(self): self.result = self.value(self.items) diff --git a/openpype/hosts/flame/api/style.css b/openpype/hosts/flame/api/style.css deleted file mode 100644 index 21e4750513..0000000000 --- a/openpype/hosts/flame/api/style.css +++ /dev/null @@ -1,69 +0,0 @@ -QWidget { - font: 14px "Discreet"; -} - -QSpinBox { - padding: 2; - max-width: 8em; -} - -QLineEdit { - color: #9a9a9a; - background-color: #373e47; - selection-color: #262626; - selection-background-color: #b8b1a7; - font: 14px "Discreet" -} -QLineEdit:focus { - background-color: #474e58 -} - -QLineEdit:disabled { - color: #6a6a6a; - background-color: #373737 -} - -QPushButton { - color: #9a9a9a; - background-color: #424142; - border-top: 1px inset #555555; - border-bottom: 1px inset black; - font: 14px "Discreet" -} - -QPushButton:pressed { - color: #d9d9d9; - background-color: #4f4f4f; - border-top: 1px inset #666666; - font: italic -} - -QPushButton:disabled { - color: #747474; - background-color: #353535; - border-top: 1px solid #444444; - border-bottom: 1px solid #242424 -} - -QVBoxLayout { - min-width: 15em; - background-color: #313131 -} - -QComboBox { - min-width: 8em; -} - -#sectionContent { - background-color: #2E2D2D; -} - -QLabel { - color: #9a9a9a; - border-bottom: 1px inset #282828; - font: 14px "Discreet" -} - -QLabel:disabled { - color: #6a6a6a -} \ No newline at end of file From 16d920028ce4fe9492be653ffdedc8b26756310d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Jan 2022 17:04:24 +0100 Subject: [PATCH 26/51] Flame: adding closing event to creator gui --- openpype/hosts/flame/api/plugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index b4fc75fe00..68bdbbe510 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -93,6 +93,10 @@ class CreatorWidget(QtWidgets.QDialog): self.result = None self.close() + def closeEvent(self, event): + self.result = None + event.accept() + def value(self, data, new_data=None): new_data = new_data or dict() for k, v in data.items(): From 169b896ef40303b375003e5a2b3d63701894954f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 14:26:06 +0100 Subject: [PATCH 27/51] flame: refactory api calls --- openpype/hosts/flame/__init__.py | 121 ------------------ openpype/hosts/flame/api/__init__.py | 114 +++++++++++++++++ openpype/hosts/flame/api/constants.py | 24 ++++ openpype/hosts/flame/api/lib.py | 52 ++++---- openpype/hosts/flame/api/menu.py | 10 +- openpype/hosts/flame/api/pipeline.py | 11 +- openpype/hosts/flame/api/plugin.py | 28 ++-- .../hosts/flame/api/scripts/wiretap_com.py | 4 +- .../api/utility_scripts/openpype_in_flame.py | 45 +++---- openpype/hosts/flame/api/utils.py | 2 +- openpype/hosts/flame/api/workio.py | 2 +- openpype/hosts/flame/hooks/pre_flame_setup.py | 3 +- openpype/hosts/flame/otio/flame_export.py | 2 +- .../flame/plugins/create/create_shot_clip.py | 16 +-- .../plugins/publish/collect_test_selection.py | 18 ++- 15 files changed, 238 insertions(+), 214 deletions(-) create mode 100644 openpype/hosts/flame/api/constants.py diff --git a/openpype/hosts/flame/__init__.py b/openpype/hosts/flame/__init__.py index da42b313aa..02befa76e2 100644 --- a/openpype/hosts/flame/__init__.py +++ b/openpype/hosts/flame/__init__.py @@ -1,126 +1,5 @@ -from .api.utils import ( - setup -) - -from .api.pipeline import ( - install, - uninstall, - ls, - containerise, - update_container, - remove_instance, - list_instances, - imprint, - maintained_selection -) - -from .api.lib import ( - FlameAppFramework, - maintain_current_timeline, - get_project_manager, - get_current_project, - get_current_sequence, - create_bin, - create_segment_data_marker, - get_segment_data_marker, - set_segment_data_marker, - set_publish_attribute, - get_publish_attribute, - get_sequence_segments, - maintained_segment_selection, - reset_segment_selection, - get_segment_attributes -) - -from .api.menu import ( - FlameMenuProjectConnect, - FlameMenuTimeline -) - -from .api.workio import ( - open_file, - save_file, - current_file, - has_unsaved_changes, - file_extensions, - work_root -) - import os HOST_DIR = os.path.dirname( os.path.abspath(__file__) ) -API_DIR = os.path.join(HOST_DIR, "api") -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") - -app_framework = None -apps = [] -selection = None - - -__all__ = [ - "HOST_DIR", - "API_DIR", - "PLUGINS_DIR", - "PUBLISH_PATH", - "LOAD_PATH", - "CREATE_PATH", - "INVENTORY_PATH", - "INVENTORY_PATH", - - "app_framework", - "apps", - "selection", - - # pipeline - "install", - "uninstall", - "ls", - "containerise", - "update_container", - "reload_pipeline", - "maintained_selection", - "remove_instance", - "list_instances", - "imprint", - "maintained_selection", - - # utils - "setup", - - # lib - "FlameAppFramework", - "maintain_current_timeline", - "get_project_manager", - "get_current_project", - "get_current_sequence", - "create_bin", - "create_segment_data_marker", - "get_segment_data_marker", - "set_segment_data_marker", - "set_publish_attribute", - "get_publish_attribute", - "get_sequence_segments", - "maintained_segment_selection", - "reset_segment_selection", - "get_segment_attributes" - - # menu - "FlameMenuProjectConnect", - "FlameMenuTimeline", - - # plugin - - # workio - "open_file", - "save_file", - "current_file", - "has_unsaved_changes", - "file_extensions", - "work_root" -] diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index 50a6b3f098..c8660aafc4 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -1,3 +1,117 @@ """ OpenPype Autodesk Flame api """ +from .constants import ( + COLOR_MAP, + MARKER_NAME, + MARKER_COLOR, + MARKER_DURATION, + MARKER_PUBLISH_DEFAULT +) +from .lib import ( + CTX, + FlameAppFramework, + maintain_current_timeline, + get_project_manager, + get_current_project, + get_current_sequence, + create_bin, + create_segment_data_marker, + get_segment_data_marker, + set_segment_data_marker, + set_publish_attribute, + get_publish_attribute, + get_sequence_segments, + maintained_segment_selection, + reset_segment_selection, + get_segment_attributes +) +from .utils import ( + setup +) +from .pipeline import ( + install, + uninstall, + ls, + containerise, + update_container, + remove_instance, + list_instances, + imprint, + maintained_selection +) +from .menu import ( + FlameMenuProjectConnect, + FlameMenuTimeline +) +from .plugin import ( + Creator, + PublishableClip +) +from .workio import ( + open_file, + save_file, + current_file, + has_unsaved_changes, + file_extensions, + work_root +) + +__all__ = [ + # constants + "COLOR_MAP", + "MARKER_NAME", + "MARKER_COLOR", + "MARKER_DURATION", + "MARKER_PUBLISH_DEFAULT", + + # lib + "CTX", + "FlameAppFramework", + "maintain_current_timeline", + "get_project_manager", + "get_current_project", + "get_current_sequence", + "create_bin", + "create_segment_data_marker", + "get_segment_data_marker", + "set_segment_data_marker", + "set_publish_attribute", + "get_publish_attribute", + "get_sequence_segments", + "maintained_segment_selection", + "reset_segment_selection", + "get_segment_attributes", + + # pipeline + "install", + "uninstall", + "ls", + "containerise", + "update_container", + "reload_pipeline", + "maintained_selection", + "remove_instance", + "list_instances", + "imprint", + "maintained_selection", + + # utils + "setup", + + # menu + "FlameMenuProjectConnect", + "FlameMenuTimeline", + + # plugin + "Creator", + "PublishableClip", + + # workio + "open_file", + "save_file", + "current_file", + "has_unsaved_changes", + "file_extensions", + "work_root" +] diff --git a/openpype/hosts/flame/api/constants.py b/openpype/hosts/flame/api/constants.py new file mode 100644 index 0000000000..1833031e13 --- /dev/null +++ b/openpype/hosts/flame/api/constants.py @@ -0,0 +1,24 @@ + +""" +OpenPype Flame api constances +""" +# OpenPype marker workflow variables +MARKER_NAME = "OpenPypeData" +MARKER_DURATION = 0 +MARKER_COLOR = "cyan" +MARKER_PUBLISH_DEFAULT = False + +# OpenPype color definitions +COLOR_MAP = { + "red": (1.0, 0.0, 0.0), + "orange": (1.0, 0.5, 0.0), + "yellow": (1.0, 1.0, 0.0), + "pink": (1.0, 0.5, 1.0), + "white": (1.0, 1.0, 1.0), + "green": (0.0, 1.0, 0.0), + "cyan": (0.0, 1.0, 1.0), + "blue": (0.0, 0.0, 1.0), + "purple": (0.5, 0.0, 0.5), + "magenta": (0.5, 0.0, 1.0), + "black": (0.0, 0.0, 0.0) +} diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index e5642dd6f9..ccc664ce63 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -5,30 +5,24 @@ import json import pickle import contextlib from pprint import pformat - +from .constants import ( + MARKER_COLOR, + MARKER_DURATION, + MARKER_NAME, + COLOR_MAP, + MARKER_PUBLISH_DEFAULT +) from openpype.api import Logger -log = Logger().get_logger(__name__) +log = Logger.get_logger(__name__) + + +class CTX: + # singleton used for passing data between api modules + app_framework = None + apps = [] + selection = None -class ctx: - # OpenPype marker workflow variables - marker_name = "OpenPypeData" - marker_duration = 0 - marker_color = "cyan" - publish_default = False - color_map = { - "red": (1.0, 0.0, 0.0), - "orange": (1.0, 0.5, 0.0), - "yellow": (1.0, 1.0, 0.0), - "pink": (1.0, 0.5, 1.0), - "white": (1.0, 1.0, 1.0), - "green": (0.0, 1.0, 0.0), - "cyan": (0.0, 1.0, 1.0), - "blue": (0.0, 0.0, 1.0), - "purple": (0.5, 0.0, 0.5), - "magenta": (0.5, 0.0, 1.0), - "black": (0.0, 0.0, 0.0) -} @contextlib.contextmanager def io_preferences_file(klass, filepath, write=False): @@ -379,7 +373,8 @@ def get_segment_data_marker(segment, with_marker=None): color = marker.colour.get_value() name = marker.name.get_value() - if name == ctx.marker_name and color == ctx.color_map[ctx.marker_color]: + if (name == MARKER_NAME) and ( + color == COLOR_MAP[MARKER_COLOR]): if not with_marker: return json.loads(comment) else: @@ -443,8 +438,8 @@ def get_publish_attribute(segment): tag_data = get_segment_data_marker(segment) if not tag_data: - set_publish_attribute(segment, ctx.publish_default) - return ctx.publish_default + set_publish_attribute(segment, MARKER_PUBLISH_DEFAULT) + return MARKER_PUBLISH_DEFAULT return tag_data["publish"] @@ -465,14 +460,15 @@ def create_segment_data_marker(segment): # create marker marker = segment.create_marker(start_frame) # set marker name - marker.name = ctx.marker_name + marker.name = MARKER_NAME # set duration - marker.duration = ctx.marker_duration + marker.duration = MARKER_DURATION # set colour - marker.colour = ctx.color_map[ctx.marker_color] # Red + marker.colour = COLOR_MAP[MARKER_COLOR] # Red return marker + def get_sequence_segments(sequence, selected=False): segments = [] # loop versions in sequence @@ -485,7 +481,7 @@ def get_sequence_segments(sequence, selected=False): # loop all segment in remaining tracks for segment in track.segments: # ignore all segments not selected - if segment.selected != True and selected == True: + if segment.selected is not True and selected is True: continue # add it to original selection segments.append(segment) diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index fef6dbfa35..642c40a7df 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -1,7 +1,7 @@ import os from Qt import QtWidgets from copy import deepcopy - +from pprint import pformat from openpype.tools.utils.host_tools import HostToolsHelper menu_group_name = 'OpenPype' @@ -26,9 +26,11 @@ default_flame_export_presets = { def callback_selection(selection, function): - import openpype.hosts.flame as opflame - opflame.selection = selection - print(opflame.selection) + import openpype.hosts.flame.api as opfapi + opfapi.CTX.selection = selection + print("Hook Selection: \n\t{}".format( + pformat({type(item): item.name for item in CTX.selection}) + )) function() diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index ee0e12584a..5333a07210 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -1,6 +1,7 @@ """ Basic avalon integration """ +import os import contextlib from avalon import api as avalon from pyblish import api as pyblish @@ -11,10 +12,18 @@ from .lib import ( maintained_segment_selection, get_current_sequence ) +from .. import HOST_DIR + +API_DIR = os.path.join(HOST_DIR, "api") +PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") AVALON_CONTAINERS = "AVALON_CONTAINERS" -log = Logger().get_logger(__name__) +log = Logger.get_logger(__name__) def install(): diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 68bdbbe510..4f71f9424e 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -1,13 +1,17 @@ import re -import os from Qt import QtWidgets, QtCore import openpype.api as openpype from openpype import style -import openpype.hosts.flame as opflame -from . import lib, pipeline +from . import selection as opfapi_selection +from . import ( + lib as flib, + pipeline as fpipeline, + constants +) + from copy import deepcopy -log = openpype.Logger().get_logger(__name__) +log = openpype.Logger.get_logger(__name__) class CreatorWidget(QtWidgets.QDialog): @@ -283,7 +287,7 @@ class Spacer(QtWidgets.QWidget): class Creator(openpype.Creator): """Creator class wrapper """ - clip_color = lib.ctx.color_map["purple"] + clip_color = constants.COLOR_MAP["purple"] rename_index = None def __init__(self, *args, **kwargs): @@ -292,13 +296,13 @@ class Creator(openpype.Creator): "flame"]["create"].get(self.__class__.__name__, {}) # adding basic current context flame objects - self.project = lib.get_current_project() - self.sequence = lib.get_current_sequence(opflame.selection) + self.project = flib.get_current_project() + self.sequence = flib.get_current_sequence(opfapi_selection) if (self.options or {}).get("useSelection"): - self.selected = lib.get_sequence_segments(self.sequence, True) + self.selected = flib.get_sequence_segments(self.sequence, True) else: - self.selected = lib.get_sequence_segments(self.sequence) + self.selected = flib.get_sequence_segments(self.sequence) self.widget = CreatorWidget @@ -345,10 +349,10 @@ class PublishableClip: # get main parent objects self.current_segment = segment - sequence_name = lib.get_current_sequence([segment]).name.get_value() + sequence_name = flib.get_current_sequence([segment]).name.get_value() self.sequence_name = str(sequence_name).replace(" ", "_") - self.clip_data = lib.get_segment_attributes(segment) + self.clip_data = flib.get_segment_attributes(segment) # segment (clip) main attributes self.cs_name = self.clip_data["segment_name"] self.cs_index = int(self.clip_data["segment"]) @@ -406,7 +410,7 @@ class PublishableClip: self.marker_data.update({"reviewTrack": None}) # create pype tag on track_item and add data - pipeline.imprint(self.current_segment, self.marker_data) + fpipeline.imprint(self.current_segment, self.marker_data) return self.current_segment diff --git a/openpype/hosts/flame/api/scripts/wiretap_com.py b/openpype/hosts/flame/api/scripts/wiretap_com.py index f1b5ab2236..0cda25804b 100644 --- a/openpype/hosts/flame/api/scripts/wiretap_com.py +++ b/openpype/hosts/flame/api/scripts/wiretap_com.py @@ -16,7 +16,7 @@ if not FLAME_V: raise KeyError("Missing key in environment `OPENPYPE_FLAME_VERSION`") try: - from libwiretapPythonClientAPI import ( + from libwiretapPythonClientAPI import ( # noqa WireTapClientInit) except ImportError: flame_python_path = "/opt/Autodesk/flame_{}/python".format(FLAME_V) @@ -26,7 +26,7 @@ except ImportError: sys.path.append(flame_python_path) - from libwiretapPythonClientAPI import ( + from libwiretapPythonClientAPI import ( # noqa WireTapClientInit, WireTapClientUninit, WireTapNodeHandle, diff --git a/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py b/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py index c5fa881f3c..6e7cebd997 100644 --- a/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py +++ b/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py @@ -5,17 +5,14 @@ from pprint import pformat import atexit import openpype import avalon -import openpype.hosts.flame as opflame - -flh = sys.modules[__name__] -flh._project = None +import openpype.hosts.flame.api as opfapi def openpype_install(): """Registering OpenPype in context """ openpype.install() - avalon.api.install(opflame) + avalon.api.install(opfapi) print("Avalon registred hosts: {}".format( avalon.api.registered_host())) @@ -48,19 +45,19 @@ sys.excepthook = exeption_handler def cleanup(): """Cleaning up Flame framework context """ - if opflame.apps: + if opfapi.CTX.apps: print('`{}` cleaning up apps:\n {}\n'.format( - __file__, pformat(opflame.apps))) - while len(opflame.apps): - app = opflame.apps.pop() + __file__, pformat(opfapi.CTX.apps))) + while len(opfapi.CTX.apps): + app = opfapi.CTX.apps.pop() print('`{}` removing : {}'.format(__file__, app.name)) del app - opflame.apps = [] + opfapi.CTX.apps = [] - if opflame.app_framework: - print('PYTHON\t: %s cleaning up' % opflame.app_framework.bundle_name) - opflame.app_framework.save_prefs() - opflame.app_framework = None + if opfapi.CTX.app_framework: + print('PYTHON\t: %s cleaning up' % opfapi.CTX.app_framework.bundle_name) + opfapi.CTX.app_framework.save_prefs() + opfapi.CTX.app_framework = None atexit.register(cleanup) @@ -69,9 +66,9 @@ atexit.register(cleanup) def load_apps(): """Load available apps into Flame framework """ - opflame.apps.append(opflame.FlameMenuProjectConnect(opflame.app_framework)) - opflame.apps.append(opflame.FlameMenuTimeline(opflame.app_framework)) - opflame.app_framework.log.info("Apps are loaded") + opfapi.CTX.apps.append(opfapi.FlameMenuProjectConnect(opfapi.CTX.app_framework)) + opfapi.CTX.apps.append(opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) + opfapi.CTX.app_framework.log.info("Apps are loaded") def project_changed_dict(info): @@ -89,10 +86,10 @@ def app_initialized(parent=None): Args: parent (obj, optional): Parent object. Defaults to None. """ - opflame.app_framework = opflame.FlameAppFramework() + opfapi.CTX.app_framework = opfapi.FlameAppFramework() print("{} initializing".format( - opflame.app_framework.bundle_name)) + opfapi.CTX.app_framework.bundle_name)) load_apps() @@ -131,15 +128,15 @@ def _build_app_menu(app_name): # first find the relative appname app = None - for _app in opflame.apps: + for _app in opfapi.CTX.apps: if _app.__class__.__name__ == app_name: app = _app if app: menu.append(app.build_menu()) - if opflame.app_framework: - menu_auto_refresh = opflame.app_framework.prefs_global.get( + if opfapi.CTX.app_framework: + menu_auto_refresh = opfapi.CTX.app_framework.prefs_global.get( 'menu_auto_refresh', {}) if menu_auto_refresh.get('timeline_menu', True): try: @@ -163,8 +160,8 @@ def project_saved(project_name, save_time, is_auto_save): save_time (str): time when it was saved is_auto_save (bool): autosave is on or off """ - if opflame.app_framework: - opflame.app_framework.save_prefs() + if opfapi.CTX.app_framework: + opfapi.CTX.app_framework.save_prefs() def get_main_menu_custom_ui_actions(): diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index 8ed8613b15..b9899900f5 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -5,7 +5,7 @@ Flame utils for syncing scripts import os import shutil from openpype.api import Logger -log = Logger().get_logger(__name__) +log = Logger.get_logger(__name__) def _sync_utility_scripts(env=None): diff --git a/openpype/hosts/flame/api/workio.py b/openpype/hosts/flame/api/workio.py index d2e2408798..0c96c0752a 100644 --- a/openpype/hosts/flame/api/workio.py +++ b/openpype/hosts/flame/api/workio.py @@ -8,7 +8,7 @@ from openpype.api import Logger # ) -log = Logger().get_logger(__name__) +log = Logger.get_logger(__name__) exported_projet_ext = ".otoc" diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index e7ef856907..5e0ead9414 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -6,6 +6,7 @@ import socket from openpype.lib import ( PreLaunchHook, get_openpype_username) from openpype.hosts import flame as opflame +import openpype.hosts.flame.api as opfapi import openpype from pprint import pformat @@ -79,7 +80,7 @@ class FlamePrelaunch(PreLaunchHook): app_arguments = self._get_launch_arguments(data_to_script) - opflame.setup(self.launch_context.env) + opfapi.setup(self.launch_context.env) self.launch_context.launch_args.extend(app_arguments) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index aea1f387e8..bea30b58bd 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -11,7 +11,7 @@ from . import utils import flame from pprint import pformat -reload(utils) # noqa +reload(utils) # type: ignore log = logging.getLogger(__name__) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 45c4557dad..70b2908bec 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -1,9 +1,8 @@ from copy import deepcopy -import openpype.hosts.flame as opflame -import openpype.hosts.flame.api.plugin as fplugin -import openpype.hosts.flame.api.lib as flib -reload(fplugin) -reload(flib) +import openpype.hosts.flame.api as opfapi + +reload(opfapi) # noqa + def _get_video_track_names(sequence): track_names = [] @@ -13,7 +12,8 @@ def _get_video_track_names(sequence): return track_names -class CreateShotClip(fplugin.Creator): + +class CreateShotClip(opfapi.Creator): """Publishable clip""" label = "Create Publishable Clip" @@ -22,7 +22,7 @@ class CreateShotClip(fplugin.Creator): defaults = ["Main"] gui_tracks = _get_video_track_names( - flib.get_current_sequence(opflame.selection) + opfapi.get_current_sequence(opfapi.CTX.selection) ) gui_name = "Pype publish attributes creator" gui_info = "Define sequential rename and fill hierarchy data." @@ -267,4 +267,4 @@ class CreateShotClip(fplugin.Creator): self.rename_index = i # convert track item to timeline media pool item - fplugin.PublishableClip(self, segment, **kwargs).convert() + opfapi.PublishableClip(self, segment, **kwargs).convert() diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 29ca08d9b5..97de4e8dde 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -1,12 +1,10 @@ import os import pyblish.api -import openpype.hosts.flame as opflame +import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export as otio_export -from openpype.hosts.flame.api import lib, pipeline from pprint import pformat -reload(lib) # noqa -reload(pipeline) # noqa -reload(otio_export) # noqa +reload(opfapi) # type: ignore +reload(otio_export) # type: ignore @pyblish.api.log @@ -20,9 +18,9 @@ class CollectTestSelection(pyblish.api.ContextPlugin): def process(self, context): self.log.info( - "Active Selection: {}".format(opflame.selection)) + "Active Selection: {}".format(opfapi.CTX.selection)) - sequence = lib.get_current_sequence(opflame.selection) + sequence = opfapi.get_current_sequence(opfapi.CTX.selection) self.test_imprint_data(sequence) self.test_otio_export(sequence) @@ -43,15 +41,15 @@ class CollectTestSelection(pyblish.api.ContextPlugin): self.log.info("Otio exported to: {}".format(export_path)) def test_imprint_data(self, sequence): - with lib.maintained_segment_selection(sequence) as selected_segments: - for segment in selected_segments: + with opfapi.maintained_segment_selection(sequence) as sel_segments: + for segment in sel_segments: if str(segment.name)[1:-1] == "": continue self.log.debug("Segment with OpenPypeData: {}".format( segment.name)) - pipeline.imprint(segment, { + opfapi.imprint(segment, { 'asset': segment.name.get_value(), 'family': 'render', 'subset': 'subsetMain' From 8121a532db97e45f8414a457678d7e250f703acc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 14:40:32 +0100 Subject: [PATCH 28/51] hound_ suggestions --- openpype/hosts/flame/api/menu.py | 2 +- openpype/hosts/flame/api/pipeline.py | 3 ++- .../hosts/flame/api/utility_scripts/openpype_in_flame.py | 7 +++++-- openpype/hosts/flame/hooks/pre_flame_setup.py | 1 - openpype/hosts/flame/otio/flame_export.py | 2 +- .../hosts/flame/plugins/publish/collect_test_selection.py | 6 +++--- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index 642c40a7df..c4a18496d3 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -29,7 +29,7 @@ def callback_selection(selection, function): import openpype.hosts.flame.api as opfapi opfapi.CTX.selection = selection print("Hook Selection: \n\t{}".format( - pformat({type(item): item.name for item in CTX.selection}) + pformat({type(item): item.name for item in opfapi.CTX.selection}) )) function() diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 5333a07210..f454c33209 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -163,6 +163,7 @@ def imprint(segment, data=None): # add publish attribute set_publish_attribute(segment, True) + @contextlib.contextmanager def maintained_selection(): import flame @@ -175,4 +176,4 @@ def maintained_selection(): with maintained_segment_selection(sequence): yield finally: - pass \ No newline at end of file + pass diff --git a/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py b/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py index 6e7cebd997..c385fbb8cb 100644 --- a/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py +++ b/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py @@ -55,7 +55,9 @@ def cleanup(): opfapi.CTX.apps = [] if opfapi.CTX.app_framework: - print('PYTHON\t: %s cleaning up' % opfapi.CTX.app_framework.bundle_name) + print('openpype\t: {} cleaning up'.format( + opfapi.CTX.app_framework.bundle_name) + ) opfapi.CTX.app_framework.save_prefs() opfapi.CTX.app_framework = None @@ -66,7 +68,8 @@ atexit.register(cleanup) def load_apps(): """Load available apps into Flame framework """ - opfapi.CTX.apps.append(opfapi.FlameMenuProjectConnect(opfapi.CTX.app_framework)) + opfapi.CTX.apps.append( + opfapi.FlameMenuProjectConnect(opfapi.CTX.app_framework)) opfapi.CTX.apps.append(opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) opfapi.CTX.app_framework.log.info("Apps are loaded") diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 5e0ead9414..512433b718 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -79,7 +79,6 @@ class FlamePrelaunch(PreLaunchHook): app_arguments = self._get_launch_arguments(data_to_script) - opfapi.setup(self.launch_context.env) self.launch_context.launch_args.extend(app_arguments) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index bea30b58bd..aea1f387e8 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -11,7 +11,7 @@ from . import utils import flame from pprint import pformat -reload(utils) # type: ignore +reload(utils) # noqa log = logging.getLogger(__name__) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 97de4e8dde..0431bd1fe3 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -3,8 +3,8 @@ import pyblish.api import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export as otio_export from pprint import pformat -reload(opfapi) # type: ignore -reload(otio_export) # type: ignore +reload(opfapi) # noqa +reload(otio_export) # noqa @pyblish.api.log @@ -35,7 +35,7 @@ class CollectTestSelection(pyblish.api.ContextPlugin): otio_timeline = otio_export.create_otio_timeline(sequence) otio_export.write_to_file( otio_timeline, export_path - ) + ) self.log.info(pformat(otio_timeline)) self.log.info("Otio exported to: {}".format(export_path)) From 75b828022103a7385a5c25116d1bcb54967fa833 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 15:07:13 +0100 Subject: [PATCH 29/51] flame: review comment https://github.com/pypeclub/OpenPype/pull/2495#discussion_r779708538 --- .../hosts/flame/api/scripts/wiretap_com.py | 33 +++++-------------- openpype/hosts/flame/hooks/pre_flame_setup.py | 20 ++++++++--- .../system_settings/applications.json | 3 +- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/flame/api/scripts/wiretap_com.py b/openpype/hosts/flame/api/scripts/wiretap_com.py index 0cda25804b..4e54dfd913 100644 --- a/openpype/hosts/flame/api/scripts/wiretap_com.py +++ b/openpype/hosts/flame/api/scripts/wiretap_com.py @@ -9,31 +9,14 @@ import json import xml.dom.minidom as minidom from copy import deepcopy import datetime - -FLAME_V = os.getenv("OPENPYPE_FLAME_VERSION") - -if not FLAME_V: - raise KeyError("Missing key in environment `OPENPYPE_FLAME_VERSION`") - -try: - from libwiretapPythonClientAPI import ( # noqa - WireTapClientInit) -except ImportError: - flame_python_path = "/opt/Autodesk/flame_{}/python".format(FLAME_V) - flame_exe_path = ( - "/opt/Autodesk/flame_{}/bin/flame.app" - "/Contents/MacOS/startApp").format(FLAME_V) - - sys.path.append(flame_python_path) - - from libwiretapPythonClientAPI import ( # noqa - WireTapClientInit, - WireTapClientUninit, - WireTapNodeHandle, - WireTapServerHandle, - WireTapInt, - WireTapStr - ) +from libwiretapPythonClientAPI import ( # noqa + WireTapClientInit, + WireTapClientUninit, + WireTapNodeHandle, + WireTapServerHandle, + WireTapInt, + WireTapStr +) class WireTapCom(object): diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 512433b718..fc6b65c958 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -20,10 +20,8 @@ class FlamePrelaunch(PreLaunchHook): app_groups = ["flame"] # todo: replace version number with avalon launch app version - flame_python_exe = ( - "/opt/Autodesk/python/{OPENPYPE_FLAME_VERSION}" - "/bin/python2.7" - ) + flame_python_exe = os.getenv("OPENPYPE_FLAME_PYTHON_EXEC") + flame_pythonpath = os.getenv("OPENPYPE_FLAME_PYTHONPATH") wtc_script_path = os.path.join( opflame.HOST_DIR, "api", "scripts", "wiretap_com.py") @@ -60,7 +58,6 @@ class FlamePrelaunch(PreLaunchHook): "FieldDominance": "PROGRESSIVE" } - data_to_script = { # from settings "host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname, @@ -77,12 +74,25 @@ class FlamePrelaunch(PreLaunchHook): self.log.info(pformat(dict(_env))) self.log.info(pformat(data_to_script)) + # add to python path from settings + self._add_pythonpath() + app_arguments = self._get_launch_arguments(data_to_script) opfapi.setup(self.launch_context.env) self.launch_context.launch_args.extend(app_arguments) + def _add_pythonpath(self): + pythonpath = self.launch_context.env.get("PYTHONPATH") + + # separate it explicity by `;` that is what we use in settings + new_pythonpath = self.flame_pythonpath.split(";") + new_pythonpath += pythonpath.split(os.pathsep) + + self.launch_context.env["PYTHONPATH"] = os.pathsep.join(new_pythonpath) + + def _get_launch_arguments(self, script_data): # Dump data to string dumped_script_data = json.dumps(script_data) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 3a097d2b37..7fe0432fdf 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -130,7 +130,8 @@ "linux": [] }, "environment": { - "OPENPYPE_FLAME_VERSION": "2021" + "OPENPYPE_FLAME_PYTHON_EXEC": "/opt/Autodesk/python/2021/bin/python2.7", + "OPENPYPE_FLAME_PYTHONPATH": "/opt/Autodesk/flame_2021/python" } }, "__dynamic_keys_labels__": { From 8a7c4772aaf4838b1cdaa03bc454b848b9eacf23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 7 Jan 2022 15:09:45 +0100 Subject: [PATCH 30/51] Update openpype/hosts/flame/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/flame/api/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 4f71f9424e..b291a6ea06 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -134,9 +134,9 @@ class CreatorWidget(QtWidgets.QDialog): '.+?(?:(?<=[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, layout, type, text, **kwargs): + def create_row(self, layout, type_name, text, **kwargs): # get type attribute from qwidgets - attr = getattr(QtWidgets, type) + attr = getattr(QtWidgets, type_name) # convert label text to normal capitalized text with spaces label_text = self.camel_case_split(text) From 389e90670ed5181d9dd45211950f67f9aec1cc5e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 15:16:40 +0100 Subject: [PATCH 31/51] flame: improving creator gui --- openpype/hosts/flame/api/plugin.py | 29 ++++--------------- .../flame/plugins/create/create_shot_clip.py | 2 +- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 4f71f9424e..3f93262e6f 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -38,7 +38,7 @@ class CreatorWidget(QtWidgets.QDialog): self.content_widget = [QtWidgets.QWidget(self)] top_layout = QtWidgets.QFormLayout(self.content_widget[0]) top_layout.setObjectName("ContentLayout") - top_layout.addWidget(Spacer(5, self)) + top_layout.addSpacing(5) # first add widget tag line top_layout.addWidget(QtWidgets.QLabel(info)) @@ -202,13 +202,13 @@ class CreatorWidget(QtWidgets.QDialog): v = data[k] tool_tip = v.get("toolTip", "") if v["type"] == "dict": - # adding spacer between sections self.content_layout.append(QtWidgets.QWidget(self)) content_layout.addWidget(self.content_layout[-1]) self.content_layout[-1].setObjectName("sectionHeadline") headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) - headline.addWidget(Spacer(20, self)) + headline.addSpacing(20) + headline.addWidget(QtWidgets.QLabel(v["label"])) # adding nested layout with label @@ -225,13 +225,12 @@ class CreatorWidget(QtWidgets.QDialog): v["value"], nested_content_layout) if v["type"] == "section": - # adding spacer between sections self.content_layout.append(QtWidgets.QWidget(self)) content_layout.addWidget(self.content_layout[-1]) self.content_layout[-1].setObjectName("sectionHeadline") headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) - headline.addWidget(Spacer(20, self)) + headline.addSpacing(20) headline.addWidget(QtWidgets.QLabel(v["label"])) # adding nested layout with label @@ -267,23 +266,6 @@ class CreatorWidget(QtWidgets.QDialog): 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) - - self.setLayout(layout) - - class Creator(openpype.Creator): """Creator class wrapper """ @@ -304,7 +286,8 @@ class Creator(openpype.Creator): else: self.selected = flib.get_sequence_segments(self.sequence) - self.widget = CreatorWidget + def create_widget(self, *args, **kwargs): + return CreatorWidget(*args, **kwargs) class PublishableClip: diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 70b2908bec..7129b965ac 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -232,7 +232,7 @@ class CreateShotClip(opfapi.Creator): gui_inputs[k]["value"] = presets[k] # open widget for plugins inputs - widget = self.widget(self.gui_name, self.gui_info, gui_inputs) + widget = self.create_widget(self.gui_name, self.gui_info, gui_inputs) widget.exec_() if len(self.selected) < 1: From ae02ad0d86fc7cf1207017729db24e7b48f326ad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 15:47:35 +0100 Subject: [PATCH 32/51] flame: refactory creator plugin with abstract class --- openpype/hosts/flame/api/plugin.py | 6 +- .../flame/plugins/create/create_shot_clip.py | 413 +++++++++--------- 2 files changed, 208 insertions(+), 211 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index b0f7568e50..6122b7bf1f 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -326,10 +326,8 @@ class PublishableClip: vertical_sync_default = False driving_layer_default = "" - def __init__(self, cls, segment, **kwargs): - # populate input cls attribute onto self.[attr] - self.__dict__.update(cls.__dict__) - + def __init__(self, segment, **kwargs): + self.rename_index = kwargs["rename_index"] # get main parent objects self.current_segment = segment sequence_name = flib.get_current_sequence([segment]).name.get_value() diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 7129b965ac..866b5108fa 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -4,15 +4,6 @@ import openpype.hosts.flame.api as opfapi reload(opfapi) # noqa -def _get_video_track_names(sequence): - track_names = [] - for ver in sequence.versions: - for track in ver.tracks: - track_names.append(track.name.get_value()) - - return track_names - - class CreateShotClip(opfapi.Creator): """Publishable clip""" @@ -21,203 +12,12 @@ class CreateShotClip(opfapi.Creator): icon = "film" defaults = ["Main"] - gui_tracks = _get_video_track_names( - opfapi.get_current_sequence(opfapi.CTX.selection) - ) - gui_name = "Pype publish attributes creator" - gui_info = "Define sequential rename and fill hierarchy data." - gui_inputs = { - "renameHierarchy": { - "type": "section", - "label": "Shot Hierarchy And Rename Settings", - "target": "ui", - "order": 0, - "value": { - "hierarchy": { - "value": "{folder}/{sequence}", - "type": "QLineEdit", - "label": "Shot Parent Hierarchy", - "target": "tag", - "toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa - "order": 0}, - "clipRename": { - "value": False, - "type": "QCheckBox", - "label": "Rename clips", - "target": "ui", - "toolTip": "Renaming selected clips on fly", # noqa - "order": 1}, - "clipName": { - "value": "{sequence}{shot}", - "type": "QLineEdit", - "label": "Clip Name Template", - "target": "ui", - "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa - "order": 2}, - "countFrom": { - "value": 10, - "type": "QSpinBox", - "label": "Count sequence from", - "target": "ui", - "toolTip": "Set when the sequence number stafrom", # noqa - "order": 3}, - "countSteps": { - "value": 10, - "type": "QSpinBox", - "label": "Stepping number", - "target": "ui", - "toolTip": "What number is adding every new step", # noqa - "order": 4}, - } - }, - "hierarchyData": { - "type": "dict", - "label": "Shot Template Keywords", - "target": "tag", - "order": 1, - "value": { - "folder": { - "value": "shots", - "type": "QLineEdit", - "label": "{folder}", - "target": "tag", - "toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 0}, - "episode": { - "value": "ep01", - "type": "QLineEdit", - "label": "{episode}", - "target": "tag", - "toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 1}, - "sequence": { - "value": "sq01", - "type": "QLineEdit", - "label": "{sequence}", - "target": "tag", - "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 2}, - "track": { - "value": "{_track_}", - "type": "QLineEdit", - "label": "{track}", - "target": "tag", - "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 3}, - "shot": { - "value": "sh###", - "type": "QLineEdit", - "label": "{shot}", - "target": "tag", - "toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 4} - } - }, - "verticalSync": { - "type": "section", - "label": "Vertical Synchronization Of Attributes", - "target": "ui", - "order": 2, - "value": { - "vSyncOn": { - "value": True, - "type": "QCheckBox", - "label": "Enable Vertical Sync", - "target": "ui", - "toolTip": "Switch on if you want clips above each other to share its attributes", # noqa - "order": 0}, - "vSyncTrack": { - "value": gui_tracks, # noqa - "type": "QComboBox", - "label": "Hero track", - "target": "ui", - "toolTip": "Select driving track name which should be hero for all others", # noqa - "order": 1} - } - }, - "publishSettings": { - "type": "section", - "label": "Publish Settings", - "target": "ui", - "order": 3, - "value": { - "subsetName": { - "value": ["[ track name ]", "main", "bg", "fg", "bg", - "animatic"], - "type": "QComboBox", - "label": "Subset Name", - "target": "ui", - "toolTip": "chose subset name patern, if [ track name ] is selected, name of track layer will be used", # noqa - "order": 0}, - "subsetFamily": { - "value": ["plate", "take"], - "type": "QComboBox", - "label": "Subset Family", - "target": "ui", "toolTip": "What use of this subset is for", # noqa - "order": 1}, - "reviewTrack": { - "value": ["< none >"] + gui_tracks, - "type": "QComboBox", - "label": "Use Review Track", - "target": "ui", - "toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa - "order": 2}, - "audio": { - "value": False, - "type": "QCheckBox", - "label": "Include audio", - "target": "tag", - "toolTip": "Process subsets with corresponding audio", # noqa - "order": 3}, - "sourceResolution": { - "value": False, - "type": "QCheckBox", - "label": "Source resolution", - "target": "tag", - "toolTip": "Is resloution taken from timeline or source?", # noqa - "order": 4}, - } - }, - "frameRangeAttr": { - "type": "section", - "label": "Shot Attributes", - "target": "ui", - "order": 4, - "value": { - "workfileFrameStart": { - "value": 1001, - "type": "QSpinBox", - "label": "Workfiles Start Frame", - "target": "tag", - "toolTip": "Set workfile starting frame number", # noqa - "order": 0 - }, - "handleStart": { - "value": 0, - "type": "QSpinBox", - "label": "Handle Start", - "target": "tag", - "toolTip": "Handle at start of clip", # noqa - "order": 1 - }, - "handleEnd": { - "value": 0, - "type": "QSpinBox", - "label": "Handle End", - "target": "tag", - "toolTip": "Handle at end of clip", # noqa - "order": 2 - } - } - } - } - presets = None def process(self): # Creator copy of object attributes that are modified during `process` presets = deepcopy(self.presets) - gui_inputs = deepcopy(self.gui_inputs) + gui_inputs = self.get_gui_inputs() # get key pares from presets and match it on ui inputs for k, v in gui_inputs.items(): @@ -232,7 +32,11 @@ class CreateShotClip(opfapi.Creator): gui_inputs[k]["value"] = presets[k] # open widget for plugins inputs - widget = self.create_widget(self.gui_name, self.gui_info, gui_inputs) + widget = self.create_widget( + "Pype publish attributes creator", + "Define sequential rename and fill hierarchy data.", + gui_inputs + ) widget.exec_() if len(self.selected) < 1: @@ -242,8 +46,6 @@ class CreateShotClip(opfapi.Creator): print("Operation aborted") return - self.rename_add = 0 - # get ui output for track name for vertical sync v_sync_track = widget.result["vSyncTrack"]["value"] @@ -264,7 +66,204 @@ class CreateShotClip(opfapi.Creator): } for i, segment in enumerate(sorted_selected_segments): - self.rename_index = i - + kwargs["rename_index"] = i # convert track item to timeline media pool item - opfapi.PublishableClip(self, segment, **kwargs).convert() + opfapi.PublishableClip(segment, **kwargs).convert() + + def get_gui_inputs(self): + gui_tracks = self._get_video_track_names( + opfapi.get_current_sequence(opfapi.CTX.selection) + ) + return deepcopy({ + "renameHierarchy": { + "type": "section", + "label": "Shot Hierarchy And Rename Settings", + "target": "ui", + "order": 0, + "value": { + "hierarchy": { + "value": "{folder}/{sequence}", + "type": "QLineEdit", + "label": "Shot Parent Hierarchy", + "target": "tag", + "toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa + "order": 0}, + "clipRename": { + "value": False, + "type": "QCheckBox", + "label": "Rename clips", + "target": "ui", + "toolTip": "Renaming selected clips on fly", # noqa + "order": 1}, + "clipName": { + "value": "{sequence}{shot}", + "type": "QLineEdit", + "label": "Clip Name Template", + "target": "ui", + "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa + "order": 2}, + "countFrom": { + "value": 10, + "type": "QSpinBox", + "label": "Count sequence from", + "target": "ui", + "toolTip": "Set when the sequence number stafrom", # noqa + "order": 3}, + "countSteps": { + "value": 10, + "type": "QSpinBox", + "label": "Stepping number", + "target": "ui", + "toolTip": "What number is adding every new step", # noqa + "order": 4}, + } + }, + "hierarchyData": { + "type": "dict", + "label": "Shot Template Keywords", + "target": "tag", + "order": 1, + "value": { + "folder": { + "value": "shots", + "type": "QLineEdit", + "label": "{folder}", + "target": "tag", + "toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 0}, + "episode": { + "value": "ep01", + "type": "QLineEdit", + "label": "{episode}", + "target": "tag", + "toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 1}, + "sequence": { + "value": "sq01", + "type": "QLineEdit", + "label": "{sequence}", + "target": "tag", + "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 2}, + "track": { + "value": "{_track_}", + "type": "QLineEdit", + "label": "{track}", + "target": "tag", + "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 3}, + "shot": { + "value": "sh###", + "type": "QLineEdit", + "label": "{shot}", + "target": "tag", + "toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 4} + } + }, + "verticalSync": { + "type": "section", + "label": "Vertical Synchronization Of Attributes", + "target": "ui", + "order": 2, + "value": { + "vSyncOn": { + "value": True, + "type": "QCheckBox", + "label": "Enable Vertical Sync", + "target": "ui", + "toolTip": "Switch on if you want clips above each other to share its attributes", # noqa + "order": 0}, + "vSyncTrack": { + "value": gui_tracks, # noqa + "type": "QComboBox", + "label": "Hero track", + "target": "ui", + "toolTip": "Select driving track name which should be hero for all others", # noqa + "order": 1} + } + }, + "publishSettings": { + "type": "section", + "label": "Publish Settings", + "target": "ui", + "order": 3, + "value": { + "subsetName": { + "value": ["[ track name ]", "main", "bg", "fg", "bg", + "animatic"], + "type": "QComboBox", + "label": "Subset Name", + "target": "ui", + "toolTip": "chose subset name patern, if [ track name ] is selected, name of track layer will be used", # noqa + "order": 0}, + "subsetFamily": { + "value": ["plate", "take"], + "type": "QComboBox", + "label": "Subset Family", + "target": "ui", "toolTip": "What use of this subset is for", # noqa + "order": 1}, + "reviewTrack": { + "value": ["< none >"] + gui_tracks, + "type": "QComboBox", + "label": "Use Review Track", + "target": "ui", + "toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa + "order": 2}, + "audio": { + "value": False, + "type": "QCheckBox", + "label": "Include audio", + "target": "tag", + "toolTip": "Process subsets with corresponding audio", # noqa + "order": 3}, + "sourceResolution": { + "value": False, + "type": "QCheckBox", + "label": "Source resolution", + "target": "tag", + "toolTip": "Is resloution taken from timeline or source?", # noqa + "order": 4}, + } + }, + "frameRangeAttr": { + "type": "section", + "label": "Shot Attributes", + "target": "ui", + "order": 4, + "value": { + "workfileFrameStart": { + "value": 1001, + "type": "QSpinBox", + "label": "Workfiles Start Frame", + "target": "tag", + "toolTip": "Set workfile starting frame number", # noqa + "order": 0 + }, + "handleStart": { + "value": 0, + "type": "QSpinBox", + "label": "Handle Start", + "target": "tag", + "toolTip": "Handle at start of clip", # noqa + "order": 1 + }, + "handleEnd": { + "value": 0, + "type": "QSpinBox", + "label": "Handle End", + "target": "tag", + "toolTip": "Handle at end of clip", # noqa + "order": 2 + } + } + } + }) + + def _get_video_track_names(self, sequence): + track_names = [] + for ver in sequence.versions: + for track in ver.tracks: + track_names.append(track.name.get_value()) + + return track_names From d0faab89f96b35b297d3117bea619d2dc8cc4c5a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 15:55:53 +0100 Subject: [PATCH 33/51] flame: adding wiretap tools dir to app env var --- openpype/hosts/flame/api/scripts/wiretap_com.py | 17 +++++++---------- .../defaults/system_settings/applications.json | 3 ++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/flame/api/scripts/wiretap_com.py b/openpype/hosts/flame/api/scripts/wiretap_com.py index 4e54dfd913..2cd9a46184 100644 --- a/openpype/hosts/flame/api/scripts/wiretap_com.py +++ b/openpype/hosts/flame/api/scripts/wiretap_com.py @@ -43,6 +43,9 @@ class WireTapCom(object): self.volume_name = volume_name or "stonefs" self.group_name = group_name or "staff" + # wiretap tools dir path + self.wiretap_tools_dir = os.getenv("OPENPYPE_WIRETAP_TOOLS") + # initialize WireTap client WireTapClientInit() @@ -166,11 +169,8 @@ class WireTapCom(object): # form cmd arguments project_create_cmd = [ os.path.join( - "/opt/Autodesk/", - "wiretap", - "tools", - FLAME_V, - "wiretap_create_node", + self.wiretap_tools_dir, + "wiretap_create_node" ), '-n', os.path.join("/volumes", self.volume_name), @@ -422,11 +422,8 @@ class WireTapCom(object): color_policy = color_policy or "Legacy" project_colorspace_cmd = [ os.path.join( - "/opt/Autodesk/", - "wiretap", - "tools", - FLAME_V, - "wiretap_duplicate_node", + self.wiretap_tools_dir, + "wiretap_duplicate_node" ), "-s", "/syncolor/policies/Autodesk/{}".format(color_policy), diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 7fe0432fdf..4a8b6d82a2 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -131,7 +131,8 @@ }, "environment": { "OPENPYPE_FLAME_PYTHON_EXEC": "/opt/Autodesk/python/2021/bin/python2.7", - "OPENPYPE_FLAME_PYTHONPATH": "/opt/Autodesk/flame_2021/python" + "OPENPYPE_FLAME_PYTHONPATH": "/opt/Autodesk/flame_2021/python", + "OPENPYPE_WIRETAP_TOOLS": "/opt/Autodesk/wiretap/tools/2021" } }, "__dynamic_keys_labels__": { From a24b43451dde32333eec70d410fd8791a29fb696 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 16:18:11 +0100 Subject: [PATCH 34/51] flame: fix wrong selection import --- openpype/hosts/flame/api/__init__.py | 2 -- openpype/hosts/flame/api/lib.py | 34 ---------------------------- openpype/hosts/flame/api/plugin.py | 3 +-- 3 files changed, 1 insertion(+), 38 deletions(-) diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index c8660aafc4..dc47488dc1 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -11,7 +11,6 @@ from .constants import ( from .lib import ( CTX, FlameAppFramework, - maintain_current_timeline, get_project_manager, get_current_project, get_current_sequence, @@ -68,7 +67,6 @@ __all__ = [ # lib "CTX", "FlameAppFramework", - "maintain_current_timeline", "get_project_manager", "get_current_project", "get_current_sequence", diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index ccc664ce63..b37cc35afd 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -225,40 +225,6 @@ class FlameAppFramework(object): return True -@contextlib.contextmanager -def maintain_current_timeline(to_timeline, from_timeline=None): - """Maintain current timeline selection during context - - Attributes: - from_timeline (resolve.Timeline)[optional]: - Example: - >>> print(from_timeline.GetName()) - timeline1 - >>> print(to_timeline.GetName()) - timeline2 - - >>> with maintain_current_timeline(to_timeline): - ... print(get_current_sequence().GetName()) - timeline2 - - >>> print(get_current_sequence().GetName()) - timeline1 - """ - # todo: this is still Resolve's implementation - project = get_current_project() - working_timeline = from_timeline or project.GetCurrentTimeline() - - # swith to the input timeline - project.SetCurrentTimeline(to_timeline) - - try: - # do a work - yield - finally: - # put the original working timeline to context - project.SetCurrentTimeline(working_timeline) - - def get_project_manager(): # TODO: get_project_manager return diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 6122b7bf1f..1ae62f3a8d 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -2,7 +2,6 @@ import re from Qt import QtWidgets, QtCore import openpype.api as openpype from openpype import style -from . import selection as opfapi_selection from . import ( lib as flib, pipeline as fpipeline, @@ -279,7 +278,7 @@ class Creator(openpype.Creator): # adding basic current context flame objects self.project = flib.get_current_project() - self.sequence = flib.get_current_sequence(opfapi_selection) + self.sequence = flib.get_current_sequence(flib.CTX.selection) if (self.options or {}).get("useSelection"): self.selected = flib.get_sequence_segments(self.sequence, True) From 9913872a2a36bfac2e625e04194a994fd7f97d85 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 16:37:37 +0100 Subject: [PATCH 35/51] flame: small fixes --- openpype/hosts/flame/api/pipeline.py | 20 +++---------------- openpype/hosts/flame/hooks/pre_flame_setup.py | 18 +++++++---------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index f454c33209..9be59990d2 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -27,13 +27,6 @@ log = Logger.get_logger(__name__) def install(): - from .. import ( - PUBLISH_PATH, - LOAD_PATH, - CREATE_PATH, - INVENTORY_PATH - ) - # Disable all families except for the ones we explicitly want to see family_states = [ "imagesequence", @@ -60,13 +53,6 @@ def install(): log.info("OpenPype Flame host installed ...") def uninstall(): - from .. import ( - PUBLISH_PATH, - LOAD_PATH, - CREATE_PATH, - INVENTORY_PATH - ) - pyblish.deregister_host("flame") log.info("Deregistering Flame plug-ins..") @@ -167,11 +153,11 @@ def imprint(segment, data=None): @contextlib.contextmanager def maintained_selection(): import flame - from .. import selection + from .lib import CTX # check if segment is selected - if isinstance(selection[0], flame.PySegment): - sequence = get_current_sequence(selection) + if isinstance(CTX.selection[0], flame.PySegment): + sequence = get_current_sequence(CTX.selection) try: with maintained_segment_selection(sequence): yield diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index fc6b65c958..6c13638f35 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -19,20 +19,17 @@ class FlamePrelaunch(PreLaunchHook): """ app_groups = ["flame"] - # todo: replace version number with avalon launch app version - flame_python_exe = os.getenv("OPENPYPE_FLAME_PYTHON_EXEC") - flame_pythonpath = os.getenv("OPENPYPE_FLAME_PYTHONPATH") - wtc_script_path = os.path.join( opflame.HOST_DIR, "api", "scripts", "wiretap_com.py") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + self._env = self.launch_context.env + self.flame_python_exe = self._env["OPENPYPE_FLAME_PYTHON_EXEC"] + self.flame_pythonpath = self._env["OPENPYPE_FLAME_PYTHONPATH"] self.signature = "( {} )".format(self.__class__.__name__) def execute(self): - _env = self.launch_context.env """Hook entry method.""" project_doc = self.data["project_doc"] user_name = get_openpype_username() @@ -60,9 +57,9 @@ class FlamePrelaunch(PreLaunchHook): data_to_script = { # from settings - "host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname, - "volume_name": _env.get("FLAME_WIRETAP_VOLUME"), - "group_name": _env.get("FLAME_WIRETAP_GROUP"), + "host_name": self._env.get("FLAME_WIRETAP_HOSTNAME") or hostname, + "volume_name": self._env.get("FLAME_WIRETAP_VOLUME"), + "group_name": self._env.get("FLAME_WIRETAP_GROUP"), "color_policy": "ACES 1.1", # from project @@ -71,7 +68,7 @@ class FlamePrelaunch(PreLaunchHook): "project_data": project_data } - self.log.info(pformat(dict(_env))) + self.log.info(pformat(dict(self._env))) self.log.info(pformat(data_to_script)) # add to python path from settings @@ -92,7 +89,6 @@ class FlamePrelaunch(PreLaunchHook): self.launch_context.env["PYTHONPATH"] = os.pathsep.join(new_pythonpath) - def _get_launch_arguments(self, script_data): # Dump data to string dumped_script_data = json.dumps(script_data) From c08ad5a4cba31a37493ae041ece3a8781f20973d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 16:43:13 +0100 Subject: [PATCH 36/51] flame: adding Spacer class back to plugin creator gui --- openpype/hosts/flame/api/plugin.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 1ae62f3a8d..30a4f3dfc4 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -37,7 +37,7 @@ class CreatorWidget(QtWidgets.QDialog): self.content_widget = [QtWidgets.QWidget(self)] top_layout = QtWidgets.QFormLayout(self.content_widget[0]) top_layout.setObjectName("ContentLayout") - top_layout.addSpacing(5) + top_layout.addWidget(Spacer(5, self)) # first add widget tag line top_layout.addWidget(QtWidgets.QLabel(info)) @@ -206,8 +206,7 @@ class CreatorWidget(QtWidgets.QDialog): self.content_layout[-1].setObjectName("sectionHeadline") headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) - headline.addSpacing(20) - + headline.addWidget(Spacer(20, self)) headline.addWidget(QtWidgets.QLabel(v["label"])) # adding nested layout with label @@ -229,7 +228,7 @@ class CreatorWidget(QtWidgets.QDialog): self.content_layout[-1].setObjectName("sectionHeadline") headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) - headline.addSpacing(20) + headline.addWidget(Spacer(20, self)) headline.addWidget(QtWidgets.QLabel(v["label"])) # adding nested layout with label @@ -265,6 +264,23 @@ class CreatorWidget(QtWidgets.QDialog): 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) + + self.setLayout(layout) + + class Creator(openpype.Creator): """Creator class wrapper """ From 7baf6437f323a8b408504f51ff78bbb0e35bb499 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 16:53:09 +0100 Subject: [PATCH 37/51] flame: env not resolving when discovery --- openpype/hosts/flame/hooks/pre_flame_setup.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 6c13638f35..d5ddafde0c 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -24,12 +24,13 @@ class FlamePrelaunch(PreLaunchHook): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._env = self.launch_context.env - self.flame_python_exe = self._env["OPENPYPE_FLAME_PYTHON_EXEC"] - self.flame_pythonpath = self._env["OPENPYPE_FLAME_PYTHONPATH"] self.signature = "( {} )".format(self.__class__.__name__) def execute(self): + _env = self.launch_context.env + self.flame_python_exe = _env["OPENPYPE_FLAME_PYTHON_EXEC"] + self.flame_pythonpath = _env["OPENPYPE_FLAME_PYTHONPATH"] + """Hook entry method.""" project_doc = self.data["project_doc"] user_name = get_openpype_username() @@ -57,9 +58,9 @@ class FlamePrelaunch(PreLaunchHook): data_to_script = { # from settings - "host_name": self._env.get("FLAME_WIRETAP_HOSTNAME") or hostname, - "volume_name": self._env.get("FLAME_WIRETAP_VOLUME"), - "group_name": self._env.get("FLAME_WIRETAP_GROUP"), + "host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname, + "volume_name": _env.get("FLAME_WIRETAP_VOLUME"), + "group_name": _env.get("FLAME_WIRETAP_GROUP"), "color_policy": "ACES 1.1", # from project @@ -68,7 +69,7 @@ class FlamePrelaunch(PreLaunchHook): "project_data": project_data } - self.log.info(pformat(dict(self._env))) + self.log.info(pformat(dict(_env))) self.log.info(pformat(data_to_script)) # add to python path from settings From a271e05e8c8f30fcb0f98176e6a456f9fb09a3d8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 16:55:33 +0100 Subject: [PATCH 38/51] flame: reduction of project menu items --- openpype/hosts/flame/api/menu.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index c4a18496d3..edb71dd118 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -111,16 +111,6 @@ class FlameMenuProjectConnect(_FlameMenuApp): "name": "Workfiles ...", "execute": lambda x: self.tools_helper.show_workfiles() }) - menu['actions'].append({ - "name": "Create ...", - "execute": lambda x: callback_selection( - x, self.tools_helper.show_creator) - }) - menu['actions'].append({ - "name": "Publish ...", - "execute": lambda x: callback_selection( - x, self.tools_helper.show_publish) - }) menu['actions'].append({ "name": "Load ...", "execute": lambda x: self.tools_helper.show_loader() From 64445d8d21e8209e8894027664cc4770c19cc0aa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 17:32:18 +0100 Subject: [PATCH 39/51] flame: creator debuging --- openpype/hosts/flame/api/pipeline.py | 12 +++++++++--- openpype/hosts/flame/api/plugin.py | 16 +++++++++++----- .../flame/plugins/create/create_shot_clip.py | 1 + 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 9be59990d2..b65c85f5df 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -10,7 +10,8 @@ from .lib import ( set_segment_data_marker, set_publish_attribute, maintained_segment_selection, - get_current_sequence + get_current_sequence, + reset_segment_selection ) from .. import HOST_DIR @@ -158,8 +159,13 @@ def maintained_selection(): # check if segment is selected if isinstance(CTX.selection[0], flame.PySegment): sequence = get_current_sequence(CTX.selection) + try: - with maintained_segment_selection(sequence): + with maintained_segment_selection(sequence) as selected: yield finally: - pass + # reset all selected clips + reset_segment_selection(sequence) + # select only original selection of segments + for segment in selected: + segment.selected = True diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 30a4f3dfc4..f2e67749f2 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -89,7 +89,8 @@ class CreatorWidget(QtWidgets.QDialog): self.setStyleSheet(style.load_stylesheet()) def _on_ok_clicked(self): - self.result = self.value(self.items) + log.debug("ok is clicked: {}".format(self.items)) + self.result = self._values(self.items) self.close() def _on_cancel_clicked(self): @@ -100,7 +101,7 @@ class CreatorWidget(QtWidgets.QDialog): self.result = None event.accept() - def value(self, data, new_data=None): + def _values(self, data, new_data=None): new_data = new_data or dict() for k, v in data.items(): new_data[k] = { @@ -109,10 +110,10 @@ class CreatorWidget(QtWidgets.QDialog): } if v["type"] == "dict": new_data[k]["target"] = v["target"] - new_data[k]["value"] = self.value(v["value"]) + new_data[k]["value"] = self._values(v["value"]) if v["type"] == "section": new_data.pop(k) - new_data = self.value(v["value"], new_data) + new_data = self._values(v["value"], new_data) elif getattr(v["value"], "currentText", None): new_data[k]["target"] = v["target"] new_data[k]["value"] = v["value"].currentText() @@ -343,6 +344,8 @@ class PublishableClip: def __init__(self, segment, **kwargs): self.rename_index = kwargs["rename_index"] + self.log = kwargs["log"] + # get main parent objects self.current_segment = segment sequence_name = flib.get_current_sequence([segment]).name.get_value() @@ -369,6 +372,9 @@ class PublishableClip: # adding ui inputs if any self.ui_inputs = kwargs.get("ui_inputs", {}) + self.log.info("Inside of plugin: {}".format( + self.marker_data + )) # populate default data before we get other attributes self._populate_segment_default_data() @@ -430,7 +436,7 @@ class PublishableClip: # define ui inputs if non gui mode was used self.shot_num = self.cs_index - log.debug( + self.log.debug( "____ self.shot_num: {}".format(self.shot_num)) # ui_inputs data or default values if gui was not used diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 866b5108fa..123a1c1575 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -61,6 +61,7 @@ class CreateShotClip(opfapi.Creator): sorted_selected_segments.extend(unsorted_selected_segments) kwargs = { + "log": self.log, "ui_inputs": widget.result, "avalon": self.data } From 4097a5c0a603b9bfc10e2a500176f0a5e3b90e59 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Jan 2022 18:00:51 +0100 Subject: [PATCH 40/51] flame: better selection print --- openpype/hosts/flame/api/menu.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/menu.py b/openpype/hosts/flame/api/menu.py index edb71dd118..b7a94e7866 100644 --- a/openpype/hosts/flame/api/menu.py +++ b/openpype/hosts/flame/api/menu.py @@ -29,7 +29,9 @@ def callback_selection(selection, function): import openpype.hosts.flame.api as opfapi opfapi.CTX.selection = selection print("Hook Selection: \n\t{}".format( - pformat({type(item): item.name for item in opfapi.CTX.selection}) + pformat({ + index: (type(item), item.name) + for index, item in enumerate(opfapi.CTX.selection)}) )) function() From e268ad9de1bd6ba4a020931376b335fd7e0acf1e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jan 2022 12:11:06 +0100 Subject: [PATCH 41/51] flame: fixing selected conditions --- openpype/hosts/flame/api/lib.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index b37cc35afd..2cc9fee173 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -446,8 +446,12 @@ def get_sequence_segments(sequence, selected=False): continue # loop all segment in remaining tracks for segment in track.segments: - # ignore all segments not selected - if segment.selected is not True and selected is True: + if segment.name.get_value() == "": + continue + if ( + selected is True + and segment.selected.get_value() is not True + ): continue # add it to original selection segments.append(segment) From ca693c1666aa0e69e92193aee62781bd7dc20be1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jan 2022 12:14:39 +0100 Subject: [PATCH 42/51] flame: fixing creator plugin operation after refactory --- openpype/hosts/flame/api/plugin.py | 27 ++++++++++++++----- .../flame/plugins/create/create_shot_clip.py | 14 +++++----- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index f2e67749f2..7432d61890 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -17,6 +17,7 @@ class CreatorWidget(QtWidgets.QDialog): # output items items = dict() + _results_back = None def __init__(self, name, info, ui_inputs, parent=None): super(CreatorWidget, self).__init__(parent) @@ -88,18 +89,27 @@ class CreatorWidget(QtWidgets.QDialog): self.setStyleSheet(style.load_stylesheet()) + @classmethod + def set_results_back(cls, value): + cls._results_back = value + + @classmethod + def get_results_back(cls): + return cls._results_back + def _on_ok_clicked(self): log.debug("ok is clicked: {}".format(self.items)) - self.result = self._values(self.items) + results_back = self._values(self.items) + self.set_results_back(results_back) self.close() def _on_cancel_clicked(self): - self.result = None + self.set_results_back(None) self.close() - def closeEvent(self, event): - self.result = None - event.accept() + def showEvent(self, event): + self.set_results_back(None) + super(CreatorWidget, self).showEvent(event) def _values(self, data, new_data=None): new_data = new_data or dict() @@ -303,7 +313,9 @@ class Creator(openpype.Creator): self.selected = flib.get_sequence_segments(self.sequence) def create_widget(self, *args, **kwargs): - return CreatorWidget(*args, **kwargs) + widget = CreatorWidget(*args, **kwargs) + widget.exec_() + return widget.get_results_back() class PublishableClip: @@ -344,6 +356,7 @@ class PublishableClip: def __init__(self, segment, **kwargs): self.rename_index = kwargs["rename_index"] + self.family = kwargs["family"] self.log = kwargs["log"] # get main parent objects @@ -580,7 +593,7 @@ class PublishableClip: "hierarchyData": hierarchy_formating_data, "subset": self.subset, "family": self.subset_family, - "families": [self.data["family"]] + "families": [self.family] } def _convert_to_entity(self, type, template): diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 123a1c1575..edc3e7176c 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -1,8 +1,6 @@ from copy import deepcopy import openpype.hosts.flame.api as opfapi -reload(opfapi) # noqa - class CreateShotClip(opfapi.Creator): """Publishable clip""" @@ -32,22 +30,21 @@ class CreateShotClip(opfapi.Creator): gui_inputs[k]["value"] = presets[k] # open widget for plugins inputs - widget = self.create_widget( + results_back = self.create_widget( "Pype publish attributes creator", "Define sequential rename and fill hierarchy data.", gui_inputs ) - widget.exec_() if len(self.selected) < 1: return - if not widget.result: + if not results_back: print("Operation aborted") return # get ui output for track name for vertical sync - v_sync_track = widget.result["vSyncTrack"]["value"] + v_sync_track = results_back["vSyncTrack"]["value"] # sort selected trackItems by sorted_selected_segments = [] @@ -62,8 +59,9 @@ class CreateShotClip(opfapi.Creator): kwargs = { "log": self.log, - "ui_inputs": widget.result, - "avalon": self.data + "ui_inputs": results_back, + "avalon": self.data, + "family": self.data["family"] } for i, segment in enumerate(sorted_selected_segments): From 1454e718e7722c66c4f0d238fef1398704fc59d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jan 2022 12:15:15 +0100 Subject: [PATCH 43/51] flame: remove flame api reload destroying selection --- openpype/hosts/flame/plugins/publish/collect_test_selection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 0431bd1fe3..0c75b3204f 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -3,7 +3,6 @@ import pyblish.api import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export as otio_export from pprint import pformat -reload(opfapi) # noqa reload(otio_export) # noqa From ba382242ce3d80d221c3622f441c0f58efb55c75 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jan 2022 12:45:05 +0100 Subject: [PATCH 44/51] flame: shot number based on segment index --- openpype/hosts/flame/api/plugin.py | 9 ++++++++- .../hosts/flame/plugins/create/create_shot_clip.py | 11 +++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 7432d61890..e6165a6d7e 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -353,6 +353,7 @@ class PublishableClip: count_steps_default = 10 vertical_sync_default = False driving_layer_default = "" + index_from_segment_default = False def __init__(self, segment, **kwargs): self.rename_index = kwargs["rename_index"] @@ -462,6 +463,8 @@ class PublishableClip: self.hierarchy_data = self.ui_inputs.get( "hierarchyData", {}).get("value") or \ self.current_segment_default_data.copy() + self.index_from_segment = self.ui_inputs.get( + "segmentIndex", {}).get("value") or self.index_from_segment_default self.count_from = self.ui_inputs.get( "countFrom", {}).get("value") or self.count_from_default self.count_steps = self.ui_inputs.get( @@ -524,8 +527,12 @@ class PublishableClip: self.review_track not in self.review_track_default): # if review layer is defined and not the same as defalut self.review_layer = self.review_track + # shot num calculate - if self.rename_index == 0: + if self.index_from_segment: + # use clip index from timeline + self.shot_num = self.count_steps * self.cs_index + elif self.rename_index == 0: self.shot_num = self.count_from else: self.shot_num = self.count_from + self.count_steps diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index edc3e7176c..f055c77a89 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -101,20 +101,27 @@ class CreateShotClip(opfapi.Creator): "target": "ui", "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa "order": 2}, + "segmentIndex": { + "value": True, + "type": "QCheckBox", + "label": "Segment index", + "target": "ui", + "toolTip": "Take number from segment index", # noqa + "order": 3}, "countFrom": { "value": 10, "type": "QSpinBox", "label": "Count sequence from", "target": "ui", "toolTip": "Set when the sequence number stafrom", # noqa - "order": 3}, + "order": 4}, "countSteps": { "value": 10, "type": "QSpinBox", "label": "Stepping number", "target": "ui", "toolTip": "What number is adding every new step", # noqa - "order": 4}, + "order": 5}, } }, "hierarchyData": { From 4f612a6a169123ea9918d2f2bbb1e3816bc4b07e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Jan 2022 12:54:55 +0100 Subject: [PATCH 45/51] flame: improving previous commit --- openpype/hosts/flame/api/plugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index e6165a6d7e..f34999bcf3 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -509,7 +509,8 @@ class PublishableClip: hero_track = False # increasing steps by index of rename iteration - self.count_steps *= self.rename_index + if not self.index_from_segment: + self.count_steps *= self.rename_index hierarchy_formating_data = {} hierarchy_data = deepcopy(self.hierarchy_data) @@ -532,10 +533,11 @@ class PublishableClip: if self.index_from_segment: # use clip index from timeline self.shot_num = self.count_steps * self.cs_index - elif self.rename_index == 0: - self.shot_num = self.count_from else: - self.shot_num = self.count_from + self.count_steps + if self.rename_index == 0: + self.shot_num = self.count_from + else: + self.shot_num = self.count_from + self.count_steps # clip name sequence number _data.update({"shot": self.shot_num}) From 74a4fbfea60ff34866000a49962a26d40e6b9fd8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:22:24 +0100 Subject: [PATCH 46/51] flame: testing export of otio timeline --- .../flame/plugins/publish/collect_test_selection.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 0c75b3204f..3e3ff27035 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -1,7 +1,9 @@ import os import pyblish.api +import tempfile import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export as otio_export +import opentimelineio as otio from pprint import pformat reload(otio_export) # noqa @@ -25,16 +27,22 @@ class CollectTestSelection(pyblish.api.ContextPlugin): self.test_otio_export(sequence) def test_otio_export(self, sequence): - home_dir = os.path.expanduser("~") + test_dir = os.path.normpath( + tempfile.mkdtemp(prefix="test_pyblish_tmp_") + ) export_path = os.path.normpath( os.path.join( - home_dir, "otio_timeline_export.otio" + test_dir, "otio_timeline_export.otio" ) ) otio_timeline = otio_export.create_otio_timeline(sequence) otio_export.write_to_file( otio_timeline, export_path ) + read_timeline_otio = otio.adapters.read_from_file(export_path) + + if otio_timeline != read_timeline_otio: + raise Exception("Exported otio timeline is different from original") self.log.info(pformat(otio_timeline)) self.log.info("Otio exported to: {}".format(export_path)) From 896ba23730a0fdedbffe5820e036d058a7e36d39 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:23:56 +0100 Subject: [PATCH 47/51] flame: hound fixes --- .../hosts/flame/plugins/publish/collect_test_selection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 3e3ff27035..73401368b1 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -28,7 +28,7 @@ class CollectTestSelection(pyblish.api.ContextPlugin): def test_otio_export(self, sequence): test_dir = os.path.normpath( - tempfile.mkdtemp(prefix="test_pyblish_tmp_") + tempfile.mkdtemp(prefix="test_pyblish_tmp_") ) export_path = os.path.normpath( os.path.join( @@ -42,7 +42,7 @@ class CollectTestSelection(pyblish.api.ContextPlugin): read_timeline_otio = otio.adapters.read_from_file(export_path) if otio_timeline != read_timeline_otio: - raise Exception("Exported otio timeline is different from original") + raise Exception("Exported timeline is different from original") self.log.info(pformat(otio_timeline)) self.log.info("Otio exported to: {}".format(export_path)) From 66cedb61e929ae475ce525bb538a8d3166471a52 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:29:11 +0100 Subject: [PATCH 48/51] flame: addressing CTX.apps renamed to CTX.flame_apps --- openpype/hosts/flame/api/lib.py | 2 +- .../api/utility_scripts/openpype_in_flame.py | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 2cc9fee173..3ec57c6434 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -20,7 +20,7 @@ log = Logger.get_logger(__name__) class CTX: # singleton used for passing data between api modules app_framework = None - apps = [] + flame_apps = [] selection = None diff --git a/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py b/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py index c385fbb8cb..72614f2b5d 100644 --- a/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py +++ b/openpype/hosts/flame/api/utility_scripts/openpype_in_flame.py @@ -45,14 +45,14 @@ sys.excepthook = exeption_handler def cleanup(): """Cleaning up Flame framework context """ - if opfapi.CTX.apps: - print('`{}` cleaning up apps:\n {}\n'.format( - __file__, pformat(opfapi.CTX.apps))) - while len(opfapi.CTX.apps): - app = opfapi.CTX.apps.pop() + if opfapi.CTX.flame_apps: + print('`{}` cleaning up flame_apps:\n {}\n'.format( + __file__, pformat(opfapi.CTX.flame_apps))) + while len(opfapi.CTX.flame_apps): + app = opfapi.CTX.flame_apps.pop() print('`{}` removing : {}'.format(__file__, app.name)) del app - opfapi.CTX.apps = [] + opfapi.CTX.flame_apps = [] if opfapi.CTX.app_framework: print('openpype\t: {} cleaning up'.format( @@ -66,11 +66,12 @@ atexit.register(cleanup) def load_apps(): - """Load available apps into Flame framework + """Load available flame_apps into Flame framework """ - opfapi.CTX.apps.append( + opfapi.CTX.flame_apps.append( opfapi.FlameMenuProjectConnect(opfapi.CTX.app_framework)) - opfapi.CTX.apps.append(opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) + opfapi.CTX.flame_apps.append( + opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) opfapi.CTX.app_framework.log.info("Apps are loaded") @@ -103,7 +104,7 @@ Initialisation of the hook is starting from here First it needs to test if it can import the flame modul. This will happen only in case a project has been loaded. Then `app_initialized` will load main Framework which will load -all menu objects as apps. +all menu objects as flame_apps. """ try: @@ -131,7 +132,7 @@ def _build_app_menu(app_name): # first find the relative appname app = None - for _app in opfapi.CTX.apps: + for _app in opfapi.CTX.flame_apps: if _app.__class__.__name__ == app_name: app = _app From c025d4c8e4dc5f4c9c4f68e209e990db05f35a89 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:34:02 +0100 Subject: [PATCH 49/51] flame: removing constant True return --- openpype/hosts/flame/api/lib.py | 5 +---- openpype/hosts/flame/api/pipeline.py | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 3ec57c6434..dd212297e2 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -374,8 +374,6 @@ def set_segment_data_marker(segment, data=None): # add tag data to marker's comment marker.comment = json.dumps(data) - return True - def set_publish_attribute(segment, value): """ Set Publish attribute in input Tag object @@ -388,8 +386,7 @@ def set_publish_attribute(segment, value): tag_data["publish"] = value # set data to the publish attribute - if not set_segment_data_marker(segment, tag_data): - raise AttributeError("Not imprint data to segment") + set_segment_data_marker(segment, tag_data) def get_publish_attribute(segment): diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index b65c85f5df..30c70b491b 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -144,8 +144,7 @@ def imprint(segment, data=None): """ data = data or {} - if not set_segment_data_marker(segment, data): - raise AttributeError("Not imprint data to segment") + set_segment_data_marker(segment, data) # add publish attribute set_publish_attribute(segment, True) From 384edda56fba3ab6ea20b351a81e3fd8e5ea65e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:39:25 +0100 Subject: [PATCH 50/51] flame: improving code from suggestion --- openpype/hosts/flame/api/lib.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index dd212297e2..7788a6b3f4 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -545,16 +545,16 @@ def get_segment_attributes(segment): "source_duration", "source_in", "source_out" ] segment_attrs_data = {} - for attr in segment_attrs: - if not hasattr(segment, attr): + for attr_name in segment_attrs: + if not hasattr(segment, attr_name): continue - _value = getattr(segment, attr) - segment_attrs_data[attr] = str(_value).replace("+", ":") + attr = getattr(segment, attr_name) + segment_attrs_data[attr] = str(attr).replace("+", ":") if attr in ["record_in", "record_out"]: - clip_data[attr] = _value.relative_frame + clip_data[attr_name] = attr.relative_frame else: - clip_data[attr] = _value.frame + clip_data[attr_name] = attr.frame clip_data["segment_timecodes"] = segment_attrs_data From 95be2c3bc8fc5a91ac2f65072fd56a6a30cda872 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Jan 2022 11:16:50 +0100 Subject: [PATCH 51/51] flame: adding pathsep instead of ";" --- openpype/hosts/flame/hooks/pre_flame_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index d5ddafde0c..fe8acda257 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -85,7 +85,7 @@ class FlamePrelaunch(PreLaunchHook): pythonpath = self.launch_context.env.get("PYTHONPATH") # separate it explicity by `;` that is what we use in settings - new_pythonpath = self.flame_pythonpath.split(";") + new_pythonpath = self.flame_pythonpath.split(os.pathsep) new_pythonpath += pythonpath.split(os.pathsep) self.launch_context.env["PYTHONPATH"] = os.pathsep.join(new_pythonpath)