From 3e53a45bfadc50ae5dd2167f714ebba657edeec2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 12:20:27 +0100 Subject: [PATCH 01/34] Flame: collect timeline ocio plugin --- .../plugins/publish/precollect_workfile.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 openpype/hosts/flame/plugins/publish/precollect_workfile.py diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py new file mode 100644 index 0000000000..0533d01e00 --- /dev/null +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -0,0 +1,26 @@ +import pyblish.api +import openpype.hosts.flame.api as opfapi +from openpype.hosts.flame.otio import flame_export + + +class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): + """Inject the current working context into publish context""" + + label = "Precollect Timeline OTIO" + order = pyblish.api.CollectorOrder - 0.5 + + def process(self, context): + project = opfapi.get_current_project() + sequence = opfapi.get_current_sequence(opfapi.CTX.selection) + + # adding otio timeline to context + otio_timeline = flame_export.create_otio_timeline(sequence) + + # update context with main project attributes + context.data.update({ + "otioTimeline": otio_timeline, + "currentFile": "Flame/{}/{}".format( + project.name, sequence.name + ), + "fps": float(str(sequence.frame_rate)[:-4]) + }) From 104b57120c64d3095c492848adca11a47a958749 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 12:32:35 +0100 Subject: [PATCH 02/34] Flame: collect instance in otio timeline plugin --- .../flame/plugins/publish/precollect_workfile.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index 0533d01e00..3497d19d15 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -1,4 +1,5 @@ import pyblish.api +import avalon.api as avalon import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export @@ -10,12 +11,25 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.5 def process(self, context): + asset = avalon.Session["AVALON_ASSET"] + subset = "otioTimeline" project = opfapi.get_current_project() sequence = opfapi.get_current_sequence(opfapi.CTX.selection) # adding otio timeline to context otio_timeline = flame_export.create_otio_timeline(sequence) + instance_data = { + "name": "{}_{}".format(asset, subset), + "asset": asset, + "subset": "{}{}".format(asset, subset.capitalize()), + "family": "workfile" + } + + # create instance with workfile + instance = context.create_instance(**instance_data) + self.log.info("Creating instance: {}".format(instance)) + # update context with main project attributes context.data.update({ "otioTimeline": otio_timeline, From 9e70f67f4716d8af3956af3486ffc47256b9db96 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 12:32:53 +0100 Subject: [PATCH 03/34] Flame: exctracting otio file --- .../plugins/publish/extract_otio_file.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 openpype/hosts/flame/plugins/publish/extract_otio_file.py diff --git a/openpype/hosts/flame/plugins/publish/extract_otio_file.py b/openpype/hosts/flame/plugins/publish/extract_otio_file.py new file mode 100644 index 0000000000..7dd75974fc --- /dev/null +++ b/openpype/hosts/flame/plugins/publish/extract_otio_file.py @@ -0,0 +1,43 @@ +import os +import pyblish.api +import openpype.api +import opentimelineio as otio + + +class ExtractOTIOFile(openpype.api.Extractor): + """ + Extractor export OTIO file + """ + + label = "Extract OTIO file" + order = pyblish.api.ExtractorOrder - 0.45 + families = ["workfile"] + hosts = ["flame"] + + def process(self, instance): + # create representation data + if "representations" not in instance.data: + instance.data["representations"] = [] + + name = instance.data["name"] + staging_dir = self.staging_dir(instance) + + otio_timeline = instance.context.data["otioTimeline"] + # create otio timeline representation + otio_file_name = name + ".otio" + otio_file_path = os.path.join(staging_dir, otio_file_name) + + # export otio file to temp dir + otio.adapters.write_to_file(otio_timeline, otio_file_path) + + representation_otio = { + 'name': "otio", + 'ext': "otio", + 'files': otio_file_name, + "stagingDir": staging_dir, + } + + instance.data["representations"].append(representation_otio) + + self.log.info("Added OTIO file representation: {}".format( + representation_otio)) From 32ceb9e9a98fa662bab525be4b8a007f4e8624f6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:24:09 +0100 Subject: [PATCH 04/34] flame: enhancing code of api lib --- openpype/hosts/flame/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 2cc9fee173..787ecf4569 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -448,6 +448,8 @@ def get_sequence_segments(sequence, selected=False): for segment in track.segments: if segment.name.get_value() == "": continue + if segment.hidden: + continue if ( selected is True and segment.selected.get_value() is not True @@ -522,7 +524,7 @@ def _get_shot_tokens_values(clip, tokens): def get_segment_attributes(segment): - if str(segment.name)[1:-1] == "": + if segment.name.get_value() == "": return None # Add timeline segment to tree From 02af9b69a195dca87a109fbfd28880372f4feaf4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:25:16 +0100 Subject: [PATCH 05/34] flame: adding flameSequnce attribute to publishing context --- openpype/hosts/flame/plugins/publish/precollect_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index 3497d19d15..3d2ce97755 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -32,6 +32,7 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): # update context with main project attributes context.data.update({ + "flameSequence": sequence, "otioTimeline": otio_timeline, "currentFile": "Flame/{}/{}".format( project.name, sequence.name From 281ae76794f2c04ba9081c402b8632bb37b3cafc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:55:18 +0100 Subject: [PATCH 06/34] flame: adding functions to lib and api --- openpype/hosts/flame/api/__init__.py | 10 ++- openpype/hosts/flame/api/lib.py | 114 +++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index dc47488dc1..308682b884 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -23,7 +23,11 @@ from .lib import ( get_sequence_segments, maintained_segment_selection, reset_segment_selection, - get_segment_attributes + get_segment_attributes, + get_clips_in_reels, + get_reformated_path, + get_frame_from_path, + get_padding_from_path ) from .utils import ( setup @@ -80,6 +84,10 @@ __all__ = [ "maintained_segment_selection", "reset_segment_selection", "get_segment_attributes", + "get_clips_in_reels", + "get_reformated_path", + "get_frame_from_path", + "get_padding_from_path", # pipeline "install", diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 787ecf4569..4404f7a612 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -537,6 +537,12 @@ def get_segment_attributes(segment): "PySegment": segment } + # head and tail with forward compatibility + if segment.head: + clip_data["segment_head"] = int(segment.head) + if segment.tail: + clip_data["segment_tail"] = int(segment.tail) + # add all available shot tokens shot_tokens = _get_shot_tokens_values(segment, [ "", "", "", "", "", @@ -564,3 +570,111 @@ def get_segment_attributes(segment): clip_data["segment_timecodes"] = segment_attrs_data return clip_data + + +def get_clips_in_reels(project): + output_clips = [] + project_desktop = project.current_workspace.desktop + + for reel_group in project_desktop.reel_groups: + for reel in reel_group.reels: + for clip in reel.clips: + clip_data = { + "PyClip": clip, + "fps": float(str(clip.frame_rate)[:-4]) + } + + attrs = [ + "name", "width", "height", + "ratio", "sample_rate", "bit_depth" + ] + + for attr in attrs: + val = getattr(clip, attr) + clip_data[attr] = val + + version = clip.versions[-1] + track = version.tracks[-1] + for segment in track.segments: + segment_data = get_segment_attributes(segment) + clip_data.update(segment_data) + + output_clips.append(clip_data) + + return output_clips + + +def get_reformated_path(path, padded=True): + """ + Return fixed python expression path + + Args: + path (str): path url or simple file name + + Returns: + type: string with reformated path + + Example: + get_reformated_path("plate.1001.exr") > plate.%04d.exr + + """ + padding = get_padding_from_path(path) + found = get_frame_from_path(path) + + if not found: + log.info("Path is not sequence: {}".format(path)) + return path + + if padded: + path = path.replace(found, "%0{}d".format(padding)) + else: + path = path.replace(found, "%d") + + return path + + +def get_padding_from_path(path): + """ + Return padding number from Flame path style + + Args: + path (str): path url or simple file name + + Returns: + int: padding number + + Example: + get_padding_from_path("plate.0001.exr") > 4 + + """ + found = get_frame_from_path(path) + + if found: + return len(found) + else: + return None + + +def get_frame_from_path(path): + """ + Return sequence number from Flame path style + + Args: + path (str): path url or simple file name + + Returns: + int: sequence frame number + + Example: + def get_frame_from_path(path): + ("plate.0001.exr") > 0001 + + """ + frame_pattern = re.compile(r"[._](\d+)[.]") + + found = re.findall(frame_pattern, path) + + if found: + return found.pop() + else: + return None From 50e1cbf31e38e7923aceba97da4d1d37eee7c47c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:55:40 +0100 Subject: [PATCH 07/34] flame: adding flameProject to publishing context attributes --- openpype/hosts/flame/plugins/publish/precollect_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index 3d2ce97755..e7383ddec8 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -32,6 +32,7 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): # update context with main project attributes context.data.update({ + "flameProject": project, "flameSequence": sequence, "otioTimeline": otio_timeline, "currentFile": "Flame/{}/{}".format( From 460048ef4c1a5b6c90ef8161f6394acb85a95d0c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:58:12 +0100 Subject: [PATCH 08/34] flame: collect instances wip --- .../plugins/publish/precollect_instances.py | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 openpype/hosts/flame/plugins/publish/precollect_instances.py diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/precollect_instances.py new file mode 100644 index 0000000000..5f3b71eba4 --- /dev/null +++ b/openpype/hosts/flame/plugins/publish/precollect_instances.py @@ -0,0 +1,251 @@ +import pyblish +# import openpype +import openpype.hosts.flame.api as opfapi + +# # developer reload modules +from pprint import pformat + + +class PrecollectInstances(pyblish.api.ContextPlugin): + """Collect all Track items selection.""" + + order = pyblish.api.CollectorOrder - 0.49 + label = "Precollect Instances" + hosts = ["flame"] + + audio_track_items = [] + + def process(self, context): + project = context.data["flameProject"] + sequence = context.data["flameSequence"] + self.otio_timeline = context.data["otioTimeline"] + self.clips_in_reels = opfapi.get_clips_in_reels(project) + + # return only actually selected and enabled segments + selected_segments = opfapi.get_sequence_segments(sequence, True) + + # only return enabled segments + if not selected_segments: + selected_segments = opfapi.get_sequence_segments( + sequence) + + self.log.info( + "Processing following segments: {}".format( + [s.name for s in selected_segments])) + + # process all sellected timeline track items + for segment in selected_segments: + + clip_data = opfapi.get_segment_attributes(segment) + clip_name = clip_data["segment_name"] + self.log.debug("clip_name: {}".format(clip_name)) + + # get openpype tag data + marker_data = opfapi.get_segment_data_marker(segment) + self.log.debug("__ marker_data: {}".format(pformat(marker_data))) + + if not marker_data: + continue + + if marker_data.get("id") != "pyblish.avalon.instance": + continue + + file_path = clip_data["fpath"] + first_frame = opfapi.get_frame_from_path(file_path) or 0 + + # calculate head and tail with forward compatibility + head = clip_data.get("segment_head") + tail = clip_data.get("segment_tail") + + if not head: + head = int(clip_data["source_in"]) - int(first_frame) + if not tail: + tail = int( + clip_data["source_duration"] - ( + head + clip_data["record_duration"] + ) + ) + + # solve handles length + marker_data["handleStart"] = min( + marker_data["handleStart"], head) + marker_data["handleEnd"] = min( + marker_data["handleEnd"], tail) + + # add audio to families + with_audio = False + if marker_data.pop("audio"): + with_audio = True + + # add tag data to instance data + data = { + k: v for k, v in marker_data.items() + if k not in ("id", "applieswhole", "label") + } + + asset = marker_data["asset"] + subset = marker_data["subset"] + + # insert family into families + family = marker_data["family"] + families = [str(f) for f in marker_data["families"]] + families.insert(0, str(family)) + + # form label + label = asset + if asset != clip_name: + label += " ({})".format(clip_name) + label += " {}".format(subset) + label += " {}".format("[" + ", ".join(families) + "]") + + data.update({ + "name": "{}_{}".format(asset, subset), + "label": label, + "asset": asset, + "item": segment, + "families": families, + "publish": marker_data["publish"], + "fps": context.data["fps"], + }) + + # # otio clip data + # otio_data = self.get_otio_clip_instance_data(segment) or {} + # self.log.debug("__ otio_data: {}".format(pformat(otio_data))) + # data.update(otio_data) + # self.log.debug("__ data: {}".format(pformat(data))) + + # # add resolution + # self.get_resolution_to_data(data, context) + + # create instance + instance = context.create_instance(**data) + + # add colorspace data + instance.data.update({ + "versionData": { + "colorspace": clip_data["colour_space"], + } + }) + + # create shot instance for shot attributes create/update + self.create_shot_instance(context, clip_name, **data) + + self.log.info("Creating instance: {}".format(instance)) + self.log.info( + "_ instance.data: {}".format(pformat(instance.data))) + + if not with_audio: + continue + + # add audioReview attribute to plate instance data + # if reviewTrack is on + if marker_data.get("reviewTrack") is not None: + instance.data["reviewAudio"] = True + + def get_resolution_to_data(self, data, context): + assert data.get("otioClip"), "Missing `otioClip` data" + + # solve source resolution option + if data.get("sourceResolution", None): + otio_clip_metadata = data[ + "otioClip"].media_reference.metadata + data.update({ + "resolutionWidth": otio_clip_metadata[ + "openpype.source.width"], + "resolutionHeight": otio_clip_metadata[ + "openpype.source.height"], + "pixelAspect": otio_clip_metadata[ + "openpype.source.pixelAspect"] + }) + else: + otio_tl_metadata = context.data["otioTimeline"].metadata + data.update({ + "resolutionWidth": otio_tl_metadata["openpype.timeline.width"], + "resolutionHeight": otio_tl_metadata[ + "openpype.timeline.height"], + "pixelAspect": otio_tl_metadata[ + "openpype.timeline.pixelAspect"] + }) + + def create_shot_instance(self, context, clip_name, **data): + master_layer = data.get("heroTrack") + hierarchy_data = data.get("hierarchyData") + asset = data.get("asset") + + if not master_layer: + return + + if not hierarchy_data: + return + + asset = data["asset"] + subset = "shotMain" + + # insert family into families + family = "shot" + + # form label + label = asset + if asset != clip_name: + label += " ({}) ".format(clip_name) + label += " {}".format(subset) + label += " [{}]".format(family) + + data.update({ + "name": "{}_{}".format(asset, subset), + "label": label, + "subset": subset, + "asset": asset, + "family": family, + "families": [] + }) + + instance = context.create_instance(**data) + self.log.info("Creating instance: {}".format(instance)) + self.log.debug( + "_ instance.data: {}".format(pformat(instance.data))) + + # def get_otio_clip_instance_data(self, segment): + # """ + # Return otio objects for timeline, track and clip + + # Args: + # timeline_item_data (dict): timeline_item_data from list returned by + # resolve.get_current_timeline_items() + # otio_timeline (otio.schema.Timeline): otio object + + # Returns: + # dict: otio clip object + + # """ + # ti_track_name = segment.parent().name() + # timeline_range = self.create_otio_time_range_from_timeline_item_data( + # segment) + # for otio_clip in self.otio_timeline.each_clip(): + # track_name = otio_clip.parent().name + # parent_range = otio_clip.range_in_parent() + # if ti_track_name not in track_name: + # continue + # if otio_clip.name not in segment.name(): + # continue + # if openpype.lib.is_overlapping_otio_ranges( + # parent_range, timeline_range, strict=True): + + # # add pypedata marker to otio_clip metadata + # for marker in otio_clip.markers: + # if phiero.pype_tag_name in marker.name: + # otio_clip.metadata.update(marker.metadata) + # return {"otioClip": otio_clip} + + # return None + + # @staticmethod + # def create_otio_time_range_from_timeline_item_data(segment): + # speed = segment.playbackSpeed() + # timeline = phiero.get_current_sequence() + # frame_start = int(segment.timelineIn()) + # frame_duration = int(segment.sourceDuration() / speed) + # fps = timeline.framerate().toFloat() + + # return hiero_export.create_otio_time_range( + # frame_start, frame_duration, fps) From 4fa7eb25ffabc6f83a7af09400f42d3e61addbb3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 16:32:00 +0100 Subject: [PATCH 09/34] flame: fix selection --- openpype/hosts/flame/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 4404f7a612..a409e731e3 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -448,7 +448,7 @@ def get_sequence_segments(sequence, selected=False): for segment in track.segments: if segment.name.get_value() == "": continue - if segment.hidden: + if segment.hidden.get_value() is True: continue if ( selected is True From a326ab429040c799ac6b45683b326aba65da3fc4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:01:39 +0100 Subject: [PATCH 10/34] flame: deactivating test plugin --- openpype/hosts/flame/plugins/publish/collect_test_selection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 0c75b3204f..84fd4fafe8 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -14,6 +14,7 @@ class CollectTestSelection(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder label = "test selection" hosts = ["flame"] + active = False def process(self, context): self.log.info( From 1669f1782b08c2906dc2c0a705e66bda8031e73c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:03:03 +0100 Subject: [PATCH 11/34] flame: adding maintained selection to publish plugins --- .../plugins/publish/precollect_instances.py | 198 ++++++++---------- .../plugins/publish/precollect_workfile.py | 3 +- 2 files changed, 95 insertions(+), 106 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/precollect_instances.py index 5f3b71eba4..e302bc42a4 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_instances.py +++ b/openpype/hosts/flame/plugins/publish/precollect_instances.py @@ -21,126 +21,114 @@ class PrecollectInstances(pyblish.api.ContextPlugin): self.otio_timeline = context.data["otioTimeline"] self.clips_in_reels = opfapi.get_clips_in_reels(project) - # return only actually selected and enabled segments - selected_segments = opfapi.get_sequence_segments(sequence, True) + # process all sellected + with opfapi.maintained_segment_selection(sequence) as selected_segments: + for segment in selected_segments: + clip_data = opfapi.get_segment_attributes(segment) + clip_name = clip_data["segment_name"] + self.log.debug("clip_name: {}".format(clip_name)) - # only return enabled segments - if not selected_segments: - selected_segments = opfapi.get_sequence_segments( - sequence) + # get openpype tag data + marker_data = opfapi.get_segment_data_marker(segment) + self.log.debug("__ marker_data: {}".format(pformat(marker_data))) - self.log.info( - "Processing following segments: {}".format( - [s.name for s in selected_segments])) + if not marker_data: + continue - # process all sellected timeline track items - for segment in selected_segments: + if marker_data.get("id") != "pyblish.avalon.instance": + continue - clip_data = opfapi.get_segment_attributes(segment) - clip_name = clip_data["segment_name"] - self.log.debug("clip_name: {}".format(clip_name)) + file_path = clip_data["fpath"] + first_frame = opfapi.get_frame_from_path(file_path) or 0 - # get openpype tag data - marker_data = opfapi.get_segment_data_marker(segment) - self.log.debug("__ marker_data: {}".format(pformat(marker_data))) + # calculate head and tail with forward compatibility + head = clip_data.get("segment_head") + tail = clip_data.get("segment_tail") - if not marker_data: - continue - - if marker_data.get("id") != "pyblish.avalon.instance": - continue - - file_path = clip_data["fpath"] - first_frame = opfapi.get_frame_from_path(file_path) or 0 - - # calculate head and tail with forward compatibility - head = clip_data.get("segment_head") - tail = clip_data.get("segment_tail") - - if not head: - head = int(clip_data["source_in"]) - int(first_frame) - if not tail: - tail = int( - clip_data["source_duration"] - ( - head + clip_data["record_duration"] + if not head: + head = int(clip_data["source_in"]) - int(first_frame) + if not tail: + tail = int( + clip_data["source_duration"] - ( + head + clip_data["record_duration"] + ) ) - ) - # solve handles length - marker_data["handleStart"] = min( - marker_data["handleStart"], head) - marker_data["handleEnd"] = min( - marker_data["handleEnd"], tail) + # solve handles length + marker_data["handleStart"] = min( + marker_data["handleStart"], head) + marker_data["handleEnd"] = min( + marker_data["handleEnd"], tail) - # add audio to families - with_audio = False - if marker_data.pop("audio"): - with_audio = True + # add audio to families + with_audio = False + if marker_data.pop("audio"): + with_audio = True - # add tag data to instance data - data = { - k: v for k, v in marker_data.items() - if k not in ("id", "applieswhole", "label") - } - - asset = marker_data["asset"] - subset = marker_data["subset"] - - # insert family into families - family = marker_data["family"] - families = [str(f) for f in marker_data["families"]] - families.insert(0, str(family)) - - # form label - label = asset - if asset != clip_name: - label += " ({})".format(clip_name) - label += " {}".format(subset) - label += " {}".format("[" + ", ".join(families) + "]") - - data.update({ - "name": "{}_{}".format(asset, subset), - "label": label, - "asset": asset, - "item": segment, - "families": families, - "publish": marker_data["publish"], - "fps": context.data["fps"], - }) - - # # otio clip data - # otio_data = self.get_otio_clip_instance_data(segment) or {} - # self.log.debug("__ otio_data: {}".format(pformat(otio_data))) - # data.update(otio_data) - # self.log.debug("__ data: {}".format(pformat(data))) - - # # add resolution - # self.get_resolution_to_data(data, context) - - # create instance - instance = context.create_instance(**data) - - # add colorspace data - instance.data.update({ - "versionData": { - "colorspace": clip_data["colour_space"], + # add tag data to instance data + data = { + k: v for k, v in marker_data.items() + if k not in ("id", "applieswhole", "label") } - }) - # create shot instance for shot attributes create/update - self.create_shot_instance(context, clip_name, **data) + asset = marker_data["asset"] + subset = marker_data["subset"] - self.log.info("Creating instance: {}".format(instance)) - self.log.info( - "_ instance.data: {}".format(pformat(instance.data))) + # insert family into families + family = marker_data["family"] + families = [str(f) for f in marker_data["families"]] + families.insert(0, str(family)) - if not with_audio: - continue + # form label + label = asset + if asset != clip_name: + label += " ({})".format(clip_name) + label += " {}".format(subset) + label += " {}".format("[" + ", ".join(families) + "]") - # add audioReview attribute to plate instance data - # if reviewTrack is on - if marker_data.get("reviewTrack") is not None: - instance.data["reviewAudio"] = True + data.update({ + "name": "{}_{}".format(asset, subset), + "label": label, + "asset": asset, + "item": segment, + "families": families, + "publish": marker_data["publish"], + "fps": context.data["fps"], + }) + + # # otio clip data + # otio_data = self.get_otio_clip_instance_data(segment) or {} + # self.log.debug("__ otio_data: {}".format(pformat(otio_data))) + # data.update(otio_data) + # self.log.debug("__ data: {}".format(pformat(data))) + + # # add resolution + # self.get_resolution_to_data(data, context) + + # create instance + instance = context.create_instance(**data) + + # add colorspace data + instance.data.update({ + "versionData": { + "colorspace": clip_data["colour_space"], + } + }) + + # create shot instance for shot attributes create/update + self.create_shot_instance(context, clip_name, **data) + + self.log.info("Creating instance: {}".format(instance)) + self.log.info( + "_ instance.data: {}".format(pformat(instance.data))) + + if not with_audio: + continue + + # add audioReview attribute to plate instance data + # if reviewTrack is on + if marker_data.get("reviewTrack") is not None: + instance.data["reviewAudio"] = True def get_resolution_to_data(self, data, context): assert data.get("otioClip"), "Missing `otioClip` data" diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index e7383ddec8..aff85e22e6 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -17,7 +17,8 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): sequence = opfapi.get_current_sequence(opfapi.CTX.selection) # adding otio timeline to context - otio_timeline = flame_export.create_otio_timeline(sequence) + with opfapi.maintained_segment_selection(sequence): + otio_timeline = flame_export.create_otio_timeline(sequence) instance_data = { "name": "{}_{}".format(asset, subset), From 74958ba642643dc78988ad1b6b9fbfcaa2127148 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 12:20:27 +0100 Subject: [PATCH 12/34] Flame: collect timeline ocio plugin --- .../plugins/publish/precollect_workfile.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 openpype/hosts/flame/plugins/publish/precollect_workfile.py diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py new file mode 100644 index 0000000000..0533d01e00 --- /dev/null +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -0,0 +1,26 @@ +import pyblish.api +import openpype.hosts.flame.api as opfapi +from openpype.hosts.flame.otio import flame_export + + +class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): + """Inject the current working context into publish context""" + + label = "Precollect Timeline OTIO" + order = pyblish.api.CollectorOrder - 0.5 + + def process(self, context): + project = opfapi.get_current_project() + sequence = opfapi.get_current_sequence(opfapi.CTX.selection) + + # adding otio timeline to context + otio_timeline = flame_export.create_otio_timeline(sequence) + + # update context with main project attributes + context.data.update({ + "otioTimeline": otio_timeline, + "currentFile": "Flame/{}/{}".format( + project.name, sequence.name + ), + "fps": float(str(sequence.frame_rate)[:-4]) + }) From 4f4efea936d4a198fe1b220c07e71ae77a065621 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 12:32:35 +0100 Subject: [PATCH 13/34] Flame: collect instance in otio timeline plugin --- .../flame/plugins/publish/precollect_workfile.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index 0533d01e00..3497d19d15 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -1,4 +1,5 @@ import pyblish.api +import avalon.api as avalon import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export @@ -10,12 +11,25 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.5 def process(self, context): + asset = avalon.Session["AVALON_ASSET"] + subset = "otioTimeline" project = opfapi.get_current_project() sequence = opfapi.get_current_sequence(opfapi.CTX.selection) # adding otio timeline to context otio_timeline = flame_export.create_otio_timeline(sequence) + instance_data = { + "name": "{}_{}".format(asset, subset), + "asset": asset, + "subset": "{}{}".format(asset, subset.capitalize()), + "family": "workfile" + } + + # create instance with workfile + instance = context.create_instance(**instance_data) + self.log.info("Creating instance: {}".format(instance)) + # update context with main project attributes context.data.update({ "otioTimeline": otio_timeline, From 65fe3a28bb74b26055913fe909208fd6e97becdf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 12:32:53 +0100 Subject: [PATCH 14/34] Flame: exctracting otio file --- .../plugins/publish/extract_otio_file.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 openpype/hosts/flame/plugins/publish/extract_otio_file.py diff --git a/openpype/hosts/flame/plugins/publish/extract_otio_file.py b/openpype/hosts/flame/plugins/publish/extract_otio_file.py new file mode 100644 index 0000000000..7dd75974fc --- /dev/null +++ b/openpype/hosts/flame/plugins/publish/extract_otio_file.py @@ -0,0 +1,43 @@ +import os +import pyblish.api +import openpype.api +import opentimelineio as otio + + +class ExtractOTIOFile(openpype.api.Extractor): + """ + Extractor export OTIO file + """ + + label = "Extract OTIO file" + order = pyblish.api.ExtractorOrder - 0.45 + families = ["workfile"] + hosts = ["flame"] + + def process(self, instance): + # create representation data + if "representations" not in instance.data: + instance.data["representations"] = [] + + name = instance.data["name"] + staging_dir = self.staging_dir(instance) + + otio_timeline = instance.context.data["otioTimeline"] + # create otio timeline representation + otio_file_name = name + ".otio" + otio_file_path = os.path.join(staging_dir, otio_file_name) + + # export otio file to temp dir + otio.adapters.write_to_file(otio_timeline, otio_file_path) + + representation_otio = { + 'name': "otio", + 'ext': "otio", + 'files': otio_file_name, + "stagingDir": staging_dir, + } + + instance.data["representations"].append(representation_otio) + + self.log.info("Added OTIO file representation: {}".format( + representation_otio)) From 42bdd8db7f3c1127f376372f9c5c12bc70daad89 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:24:09 +0100 Subject: [PATCH 15/34] flame: enhancing code of api lib --- openpype/hosts/flame/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 7788a6b3f4..b5c7f2031b 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -445,6 +445,8 @@ def get_sequence_segments(sequence, selected=False): for segment in track.segments: if segment.name.get_value() == "": continue + if segment.hidden: + continue if ( selected is True and segment.selected.get_value() is not True @@ -519,7 +521,7 @@ def _get_shot_tokens_values(clip, tokens): def get_segment_attributes(segment): - if str(segment.name)[1:-1] == "": + if segment.name.get_value() == "": return None # Add timeline segment to tree From eb6c6a5c9fc96cd8596484f06ab91b5bbad1db64 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:25:16 +0100 Subject: [PATCH 16/34] flame: adding flameSequnce attribute to publishing context --- openpype/hosts/flame/plugins/publish/precollect_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index 3497d19d15..3d2ce97755 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -32,6 +32,7 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): # update context with main project attributes context.data.update({ + "flameSequence": sequence, "otioTimeline": otio_timeline, "currentFile": "Flame/{}/{}".format( project.name, sequence.name From 093015bf34b438d66c6b773c248bf7d67168a6ae Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:55:18 +0100 Subject: [PATCH 17/34] flame: adding functions to lib and api --- openpype/hosts/flame/api/__init__.py | 10 ++- openpype/hosts/flame/api/lib.py | 114 +++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index dc47488dc1..308682b884 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -23,7 +23,11 @@ from .lib import ( get_sequence_segments, maintained_segment_selection, reset_segment_selection, - get_segment_attributes + get_segment_attributes, + get_clips_in_reels, + get_reformated_path, + get_frame_from_path, + get_padding_from_path ) from .utils import ( setup @@ -80,6 +84,10 @@ __all__ = [ "maintained_segment_selection", "reset_segment_selection", "get_segment_attributes", + "get_clips_in_reels", + "get_reformated_path", + "get_frame_from_path", + "get_padding_from_path", # pipeline "install", diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index b5c7f2031b..b204230d9a 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -534,6 +534,12 @@ def get_segment_attributes(segment): "PySegment": segment } + # head and tail with forward compatibility + if segment.head: + clip_data["segment_head"] = int(segment.head) + if segment.tail: + clip_data["segment_tail"] = int(segment.tail) + # add all available shot tokens shot_tokens = _get_shot_tokens_values(segment, [ "", "", "", "", "", @@ -561,3 +567,111 @@ def get_segment_attributes(segment): clip_data["segment_timecodes"] = segment_attrs_data return clip_data + + +def get_clips_in_reels(project): + output_clips = [] + project_desktop = project.current_workspace.desktop + + for reel_group in project_desktop.reel_groups: + for reel in reel_group.reels: + for clip in reel.clips: + clip_data = { + "PyClip": clip, + "fps": float(str(clip.frame_rate)[:-4]) + } + + attrs = [ + "name", "width", "height", + "ratio", "sample_rate", "bit_depth" + ] + + for attr in attrs: + val = getattr(clip, attr) + clip_data[attr] = val + + version = clip.versions[-1] + track = version.tracks[-1] + for segment in track.segments: + segment_data = get_segment_attributes(segment) + clip_data.update(segment_data) + + output_clips.append(clip_data) + + return output_clips + + +def get_reformated_path(path, padded=True): + """ + Return fixed python expression path + + Args: + path (str): path url or simple file name + + Returns: + type: string with reformated path + + Example: + get_reformated_path("plate.1001.exr") > plate.%04d.exr + + """ + padding = get_padding_from_path(path) + found = get_frame_from_path(path) + + if not found: + log.info("Path is not sequence: {}".format(path)) + return path + + if padded: + path = path.replace(found, "%0{}d".format(padding)) + else: + path = path.replace(found, "%d") + + return path + + +def get_padding_from_path(path): + """ + Return padding number from Flame path style + + Args: + path (str): path url or simple file name + + Returns: + int: padding number + + Example: + get_padding_from_path("plate.0001.exr") > 4 + + """ + found = get_frame_from_path(path) + + if found: + return len(found) + else: + return None + + +def get_frame_from_path(path): + """ + Return sequence number from Flame path style + + Args: + path (str): path url or simple file name + + Returns: + int: sequence frame number + + Example: + def get_frame_from_path(path): + ("plate.0001.exr") > 0001 + + """ + frame_pattern = re.compile(r"[._](\d+)[.]") + + found = re.findall(frame_pattern, path) + + if found: + return found.pop() + else: + return None From 402b18640967070ad8fb2079f7ec0d92fb5a222b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:55:40 +0100 Subject: [PATCH 18/34] flame: adding flameProject to publishing context attributes --- openpype/hosts/flame/plugins/publish/precollect_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index 3d2ce97755..e7383ddec8 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -32,6 +32,7 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): # update context with main project attributes context.data.update({ + "flameProject": project, "flameSequence": sequence, "otioTimeline": otio_timeline, "currentFile": "Flame/{}/{}".format( From 70d31f2ef16b9a97e03c5f956b344c64bb25c1df Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 15:58:12 +0100 Subject: [PATCH 19/34] flame: collect instances wip --- .../plugins/publish/precollect_instances.py | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 openpype/hosts/flame/plugins/publish/precollect_instances.py diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/precollect_instances.py new file mode 100644 index 0000000000..5f3b71eba4 --- /dev/null +++ b/openpype/hosts/flame/plugins/publish/precollect_instances.py @@ -0,0 +1,251 @@ +import pyblish +# import openpype +import openpype.hosts.flame.api as opfapi + +# # developer reload modules +from pprint import pformat + + +class PrecollectInstances(pyblish.api.ContextPlugin): + """Collect all Track items selection.""" + + order = pyblish.api.CollectorOrder - 0.49 + label = "Precollect Instances" + hosts = ["flame"] + + audio_track_items = [] + + def process(self, context): + project = context.data["flameProject"] + sequence = context.data["flameSequence"] + self.otio_timeline = context.data["otioTimeline"] + self.clips_in_reels = opfapi.get_clips_in_reels(project) + + # return only actually selected and enabled segments + selected_segments = opfapi.get_sequence_segments(sequence, True) + + # only return enabled segments + if not selected_segments: + selected_segments = opfapi.get_sequence_segments( + sequence) + + self.log.info( + "Processing following segments: {}".format( + [s.name for s in selected_segments])) + + # process all sellected timeline track items + for segment in selected_segments: + + clip_data = opfapi.get_segment_attributes(segment) + clip_name = clip_data["segment_name"] + self.log.debug("clip_name: {}".format(clip_name)) + + # get openpype tag data + marker_data = opfapi.get_segment_data_marker(segment) + self.log.debug("__ marker_data: {}".format(pformat(marker_data))) + + if not marker_data: + continue + + if marker_data.get("id") != "pyblish.avalon.instance": + continue + + file_path = clip_data["fpath"] + first_frame = opfapi.get_frame_from_path(file_path) or 0 + + # calculate head and tail with forward compatibility + head = clip_data.get("segment_head") + tail = clip_data.get("segment_tail") + + if not head: + head = int(clip_data["source_in"]) - int(first_frame) + if not tail: + tail = int( + clip_data["source_duration"] - ( + head + clip_data["record_duration"] + ) + ) + + # solve handles length + marker_data["handleStart"] = min( + marker_data["handleStart"], head) + marker_data["handleEnd"] = min( + marker_data["handleEnd"], tail) + + # add audio to families + with_audio = False + if marker_data.pop("audio"): + with_audio = True + + # add tag data to instance data + data = { + k: v for k, v in marker_data.items() + if k not in ("id", "applieswhole", "label") + } + + asset = marker_data["asset"] + subset = marker_data["subset"] + + # insert family into families + family = marker_data["family"] + families = [str(f) for f in marker_data["families"]] + families.insert(0, str(family)) + + # form label + label = asset + if asset != clip_name: + label += " ({})".format(clip_name) + label += " {}".format(subset) + label += " {}".format("[" + ", ".join(families) + "]") + + data.update({ + "name": "{}_{}".format(asset, subset), + "label": label, + "asset": asset, + "item": segment, + "families": families, + "publish": marker_data["publish"], + "fps": context.data["fps"], + }) + + # # otio clip data + # otio_data = self.get_otio_clip_instance_data(segment) or {} + # self.log.debug("__ otio_data: {}".format(pformat(otio_data))) + # data.update(otio_data) + # self.log.debug("__ data: {}".format(pformat(data))) + + # # add resolution + # self.get_resolution_to_data(data, context) + + # create instance + instance = context.create_instance(**data) + + # add colorspace data + instance.data.update({ + "versionData": { + "colorspace": clip_data["colour_space"], + } + }) + + # create shot instance for shot attributes create/update + self.create_shot_instance(context, clip_name, **data) + + self.log.info("Creating instance: {}".format(instance)) + self.log.info( + "_ instance.data: {}".format(pformat(instance.data))) + + if not with_audio: + continue + + # add audioReview attribute to plate instance data + # if reviewTrack is on + if marker_data.get("reviewTrack") is not None: + instance.data["reviewAudio"] = True + + def get_resolution_to_data(self, data, context): + assert data.get("otioClip"), "Missing `otioClip` data" + + # solve source resolution option + if data.get("sourceResolution", None): + otio_clip_metadata = data[ + "otioClip"].media_reference.metadata + data.update({ + "resolutionWidth": otio_clip_metadata[ + "openpype.source.width"], + "resolutionHeight": otio_clip_metadata[ + "openpype.source.height"], + "pixelAspect": otio_clip_metadata[ + "openpype.source.pixelAspect"] + }) + else: + otio_tl_metadata = context.data["otioTimeline"].metadata + data.update({ + "resolutionWidth": otio_tl_metadata["openpype.timeline.width"], + "resolutionHeight": otio_tl_metadata[ + "openpype.timeline.height"], + "pixelAspect": otio_tl_metadata[ + "openpype.timeline.pixelAspect"] + }) + + def create_shot_instance(self, context, clip_name, **data): + master_layer = data.get("heroTrack") + hierarchy_data = data.get("hierarchyData") + asset = data.get("asset") + + if not master_layer: + return + + if not hierarchy_data: + return + + asset = data["asset"] + subset = "shotMain" + + # insert family into families + family = "shot" + + # form label + label = asset + if asset != clip_name: + label += " ({}) ".format(clip_name) + label += " {}".format(subset) + label += " [{}]".format(family) + + data.update({ + "name": "{}_{}".format(asset, subset), + "label": label, + "subset": subset, + "asset": asset, + "family": family, + "families": [] + }) + + instance = context.create_instance(**data) + self.log.info("Creating instance: {}".format(instance)) + self.log.debug( + "_ instance.data: {}".format(pformat(instance.data))) + + # def get_otio_clip_instance_data(self, segment): + # """ + # Return otio objects for timeline, track and clip + + # Args: + # timeline_item_data (dict): timeline_item_data from list returned by + # resolve.get_current_timeline_items() + # otio_timeline (otio.schema.Timeline): otio object + + # Returns: + # dict: otio clip object + + # """ + # ti_track_name = segment.parent().name() + # timeline_range = self.create_otio_time_range_from_timeline_item_data( + # segment) + # for otio_clip in self.otio_timeline.each_clip(): + # track_name = otio_clip.parent().name + # parent_range = otio_clip.range_in_parent() + # if ti_track_name not in track_name: + # continue + # if otio_clip.name not in segment.name(): + # continue + # if openpype.lib.is_overlapping_otio_ranges( + # parent_range, timeline_range, strict=True): + + # # add pypedata marker to otio_clip metadata + # for marker in otio_clip.markers: + # if phiero.pype_tag_name in marker.name: + # otio_clip.metadata.update(marker.metadata) + # return {"otioClip": otio_clip} + + # return None + + # @staticmethod + # def create_otio_time_range_from_timeline_item_data(segment): + # speed = segment.playbackSpeed() + # timeline = phiero.get_current_sequence() + # frame_start = int(segment.timelineIn()) + # frame_duration = int(segment.sourceDuration() / speed) + # fps = timeline.framerate().toFloat() + + # return hiero_export.create_otio_time_range( + # frame_start, frame_duration, fps) From da1bb80b62d8e606e5c1b5bdc1fa0a53685c3fba Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 16:32:00 +0100 Subject: [PATCH 20/34] flame: fix selection --- openpype/hosts/flame/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index b204230d9a..e53127503b 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -445,7 +445,7 @@ def get_sequence_segments(sequence, selected=False): for segment in track.segments: if segment.name.get_value() == "": continue - if segment.hidden: + if segment.hidden.get_value() is True: continue if ( selected is True From 8f786f325541e5b8282eef515789333044727a8e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:01:39 +0100 Subject: [PATCH 21/34] flame: deactivating test plugin --- openpype/hosts/flame/plugins/publish/collect_test_selection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/plugins/publish/collect_test_selection.py b/openpype/hosts/flame/plugins/publish/collect_test_selection.py index 73401368b1..9f982321cc 100644 --- a/openpype/hosts/flame/plugins/publish/collect_test_selection.py +++ b/openpype/hosts/flame/plugins/publish/collect_test_selection.py @@ -16,6 +16,7 @@ class CollectTestSelection(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder label = "test selection" hosts = ["flame"] + active = False def process(self, context): self.log.info( From 28341de97f283f51043530b581fb7a34ffb6337a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Jan 2022 17:03:03 +0100 Subject: [PATCH 22/34] flame: adding maintained selection to publish plugins --- .../plugins/publish/precollect_instances.py | 198 ++++++++---------- .../plugins/publish/precollect_workfile.py | 3 +- 2 files changed, 95 insertions(+), 106 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/precollect_instances.py index 5f3b71eba4..e302bc42a4 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_instances.py +++ b/openpype/hosts/flame/plugins/publish/precollect_instances.py @@ -21,126 +21,114 @@ class PrecollectInstances(pyblish.api.ContextPlugin): self.otio_timeline = context.data["otioTimeline"] self.clips_in_reels = opfapi.get_clips_in_reels(project) - # return only actually selected and enabled segments - selected_segments = opfapi.get_sequence_segments(sequence, True) + # process all sellected + with opfapi.maintained_segment_selection(sequence) as selected_segments: + for segment in selected_segments: + clip_data = opfapi.get_segment_attributes(segment) + clip_name = clip_data["segment_name"] + self.log.debug("clip_name: {}".format(clip_name)) - # only return enabled segments - if not selected_segments: - selected_segments = opfapi.get_sequence_segments( - sequence) + # get openpype tag data + marker_data = opfapi.get_segment_data_marker(segment) + self.log.debug("__ marker_data: {}".format(pformat(marker_data))) - self.log.info( - "Processing following segments: {}".format( - [s.name for s in selected_segments])) + if not marker_data: + continue - # process all sellected timeline track items - for segment in selected_segments: + if marker_data.get("id") != "pyblish.avalon.instance": + continue - clip_data = opfapi.get_segment_attributes(segment) - clip_name = clip_data["segment_name"] - self.log.debug("clip_name: {}".format(clip_name)) + file_path = clip_data["fpath"] + first_frame = opfapi.get_frame_from_path(file_path) or 0 - # get openpype tag data - marker_data = opfapi.get_segment_data_marker(segment) - self.log.debug("__ marker_data: {}".format(pformat(marker_data))) + # calculate head and tail with forward compatibility + head = clip_data.get("segment_head") + tail = clip_data.get("segment_tail") - if not marker_data: - continue - - if marker_data.get("id") != "pyblish.avalon.instance": - continue - - file_path = clip_data["fpath"] - first_frame = opfapi.get_frame_from_path(file_path) or 0 - - # calculate head and tail with forward compatibility - head = clip_data.get("segment_head") - tail = clip_data.get("segment_tail") - - if not head: - head = int(clip_data["source_in"]) - int(first_frame) - if not tail: - tail = int( - clip_data["source_duration"] - ( - head + clip_data["record_duration"] + if not head: + head = int(clip_data["source_in"]) - int(first_frame) + if not tail: + tail = int( + clip_data["source_duration"] - ( + head + clip_data["record_duration"] + ) ) - ) - # solve handles length - marker_data["handleStart"] = min( - marker_data["handleStart"], head) - marker_data["handleEnd"] = min( - marker_data["handleEnd"], tail) + # solve handles length + marker_data["handleStart"] = min( + marker_data["handleStart"], head) + marker_data["handleEnd"] = min( + marker_data["handleEnd"], tail) - # add audio to families - with_audio = False - if marker_data.pop("audio"): - with_audio = True + # add audio to families + with_audio = False + if marker_data.pop("audio"): + with_audio = True - # add tag data to instance data - data = { - k: v for k, v in marker_data.items() - if k not in ("id", "applieswhole", "label") - } - - asset = marker_data["asset"] - subset = marker_data["subset"] - - # insert family into families - family = marker_data["family"] - families = [str(f) for f in marker_data["families"]] - families.insert(0, str(family)) - - # form label - label = asset - if asset != clip_name: - label += " ({})".format(clip_name) - label += " {}".format(subset) - label += " {}".format("[" + ", ".join(families) + "]") - - data.update({ - "name": "{}_{}".format(asset, subset), - "label": label, - "asset": asset, - "item": segment, - "families": families, - "publish": marker_data["publish"], - "fps": context.data["fps"], - }) - - # # otio clip data - # otio_data = self.get_otio_clip_instance_data(segment) or {} - # self.log.debug("__ otio_data: {}".format(pformat(otio_data))) - # data.update(otio_data) - # self.log.debug("__ data: {}".format(pformat(data))) - - # # add resolution - # self.get_resolution_to_data(data, context) - - # create instance - instance = context.create_instance(**data) - - # add colorspace data - instance.data.update({ - "versionData": { - "colorspace": clip_data["colour_space"], + # add tag data to instance data + data = { + k: v for k, v in marker_data.items() + if k not in ("id", "applieswhole", "label") } - }) - # create shot instance for shot attributes create/update - self.create_shot_instance(context, clip_name, **data) + asset = marker_data["asset"] + subset = marker_data["subset"] - self.log.info("Creating instance: {}".format(instance)) - self.log.info( - "_ instance.data: {}".format(pformat(instance.data))) + # insert family into families + family = marker_data["family"] + families = [str(f) for f in marker_data["families"]] + families.insert(0, str(family)) - if not with_audio: - continue + # form label + label = asset + if asset != clip_name: + label += " ({})".format(clip_name) + label += " {}".format(subset) + label += " {}".format("[" + ", ".join(families) + "]") - # add audioReview attribute to plate instance data - # if reviewTrack is on - if marker_data.get("reviewTrack") is not None: - instance.data["reviewAudio"] = True + data.update({ + "name": "{}_{}".format(asset, subset), + "label": label, + "asset": asset, + "item": segment, + "families": families, + "publish": marker_data["publish"], + "fps": context.data["fps"], + }) + + # # otio clip data + # otio_data = self.get_otio_clip_instance_data(segment) or {} + # self.log.debug("__ otio_data: {}".format(pformat(otio_data))) + # data.update(otio_data) + # self.log.debug("__ data: {}".format(pformat(data))) + + # # add resolution + # self.get_resolution_to_data(data, context) + + # create instance + instance = context.create_instance(**data) + + # add colorspace data + instance.data.update({ + "versionData": { + "colorspace": clip_data["colour_space"], + } + }) + + # create shot instance for shot attributes create/update + self.create_shot_instance(context, clip_name, **data) + + self.log.info("Creating instance: {}".format(instance)) + self.log.info( + "_ instance.data: {}".format(pformat(instance.data))) + + if not with_audio: + continue + + # add audioReview attribute to plate instance data + # if reviewTrack is on + if marker_data.get("reviewTrack") is not None: + instance.data["reviewAudio"] = True def get_resolution_to_data(self, data, context): assert data.get("otioClip"), "Missing `otioClip` data" diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index e7383ddec8..aff85e22e6 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -17,7 +17,8 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): sequence = opfapi.get_current_sequence(opfapi.CTX.selection) # adding otio timeline to context - otio_timeline = flame_export.create_otio_timeline(sequence) + with opfapi.maintained_segment_selection(sequence): + otio_timeline = flame_export.create_otio_timeline(sequence) instance_data = { "name": "{}_{}".format(asset, subset), From 197b2d33a672e4ece48bfe7b6f5b38076c2209bd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Jan 2022 13:30:49 +0100 Subject: [PATCH 23/34] flame: instance collector update --- .../plugins/publish/precollect_instances.py | 151 ++++++++++-------- 1 file changed, 88 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/precollect_instances.py index e302bc42a4..fa007b3efd 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_instances.py +++ b/openpype/hosts/flame/plugins/publish/precollect_instances.py @@ -1,6 +1,7 @@ import pyblish -# import openpype +import openpype import openpype.hosts.flame.api as opfapi +from openpype.hosts.flame.otio import flame_export # # developer reload modules from pprint import pformat @@ -20,10 +21,11 @@ class PrecollectInstances(pyblish.api.ContextPlugin): sequence = context.data["flameSequence"] self.otio_timeline = context.data["otioTimeline"] self.clips_in_reels = opfapi.get_clips_in_reels(project) + self.fps = context.data["fps"] # process all sellected - with opfapi.maintained_segment_selection(sequence) as selected_segments: - for segment in selected_segments: + with opfapi.maintained_segment_selection(sequence) as segments: + for segment in segments: clip_data = opfapi.get_segment_attributes(segment) clip_name = clip_data["segment_name"] self.log.debug("clip_name: {}".format(clip_name)) @@ -38,21 +40,15 @@ class PrecollectInstances(pyblish.api.ContextPlugin): if marker_data.get("id") != "pyblish.avalon.instance": continue + # get file path file_path = clip_data["fpath"] + + # get source clip + source_clip = self._get_reel_clip(file_path) + first_frame = opfapi.get_frame_from_path(file_path) or 0 - # calculate head and tail with forward compatibility - head = clip_data.get("segment_head") - tail = clip_data.get("segment_tail") - - if not head: - head = int(clip_data["source_in"]) - int(first_frame) - if not tail: - tail = int( - clip_data["source_duration"] - ( - head + clip_data["record_duration"] - ) - ) + head, tail = self._get_head_tail(clip_data, first_frame) # solve handles length marker_data["handleStart"] = min( @@ -93,17 +89,19 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "item": segment, "families": families, "publish": marker_data["publish"], - "fps": context.data["fps"], + "fps": self.fps, + "flameSourceClip": source_clip, + "sourceFirstFrame": first_frame }) - # # otio clip data - # otio_data = self.get_otio_clip_instance_data(segment) or {} - # self.log.debug("__ otio_data: {}".format(pformat(otio_data))) - # data.update(otio_data) - # self.log.debug("__ data: {}".format(pformat(data))) + # otio clip data + otio_data = self._get_otio_clip_instance_data(clip_data) or {} + self.log.debug("__ otio_data: {}".format(pformat(otio_data))) + data.update(otio_data) + self.log.debug("__ data: {}".format(pformat(data))) - # # add resolution - # self.get_resolution_to_data(data, context) + # add resolution + self._get_resolution_to_data(data, context) # create instance instance = context.create_instance(**data) @@ -116,7 +114,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): }) # create shot instance for shot attributes create/update - self.create_shot_instance(context, clip_name, **data) + self._create_shot_instance(context, clip_name, **data) self.log.info("Creating instance: {}".format(instance)) self.log.info( @@ -130,7 +128,30 @@ class PrecollectInstances(pyblish.api.ContextPlugin): if marker_data.get("reviewTrack") is not None: instance.data["reviewAudio"] = True - def get_resolution_to_data(self, data, context): + def _get_head_tail(self, clip_data, first_frame): + # calculate head and tail with forward compatibility + head = clip_data.get("segment_head") + tail = clip_data.get("segment_tail") + + if not head: + head = int(clip_data["source_in"]) - int(first_frame) + if not tail: + tail = int( + clip_data["source_duration"] - ( + head + clip_data["record_duration"] + ) + ) + return head, tail + + def _get_reel_clip(self, path): + match_reel_clip = [ + clip for clip in self.clips_in_reels + if clip["fpath"] == path + ] + if match_reel_clip: + return match_reel_clip.pop() + + def _get_resolution_to_data(self, data, context): assert data.get("otioClip"), "Missing `otioClip` data" # solve source resolution option @@ -155,7 +176,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "openpype.timeline.pixelAspect"] }) - def create_shot_instance(self, context, clip_name, **data): + def _create_shot_instance(self, context, clip_name, **data): master_layer = data.get("heroTrack") hierarchy_data = data.get("hierarchyData") asset = data.get("asset") @@ -193,47 +214,51 @@ class PrecollectInstances(pyblish.api.ContextPlugin): self.log.debug( "_ instance.data: {}".format(pformat(instance.data))) - # def get_otio_clip_instance_data(self, segment): - # """ - # Return otio objects for timeline, track and clip + def _get_otio_clip_instance_data(self, clip_data): + """ + Return otio objects for timeline, track and clip - # Args: - # timeline_item_data (dict): timeline_item_data from list returned by - # resolve.get_current_timeline_items() - # otio_timeline (otio.schema.Timeline): otio object + Args: + timeline_item_data (dict): timeline_item_data from list returned by + resolve.get_current_timeline_items() + otio_timeline (otio.schema.Timeline): otio object - # Returns: - # dict: otio clip object + Returns: + dict: otio clip object - # """ - # ti_track_name = segment.parent().name() - # timeline_range = self.create_otio_time_range_from_timeline_item_data( - # segment) - # for otio_clip in self.otio_timeline.each_clip(): - # track_name = otio_clip.parent().name - # parent_range = otio_clip.range_in_parent() - # if ti_track_name not in track_name: - # continue - # if otio_clip.name not in segment.name(): - # continue - # if openpype.lib.is_overlapping_otio_ranges( - # parent_range, timeline_range, strict=True): + """ + segment = clip_data["PySegment"] - # # add pypedata marker to otio_clip metadata - # for marker in otio_clip.markers: - # if phiero.pype_tag_name in marker.name: - # otio_clip.metadata.update(marker.metadata) - # return {"otioClip": otio_clip} + self.log.debug( + ">> flame Track.dir: {}".format(dir(segment.parent))) + s_track_name = segment.parent.name.get_value() - # return None + timeline_range = self._create_otio_time_range_from_timeline_item_data( + clip_data) - # @staticmethod - # def create_otio_time_range_from_timeline_item_data(segment): - # speed = segment.playbackSpeed() - # timeline = phiero.get_current_sequence() - # frame_start = int(segment.timelineIn()) - # frame_duration = int(segment.sourceDuration() / speed) - # fps = timeline.framerate().toFloat() + for otio_clip in self.otio_timeline.each_clip(): + self.log.debug( + ">> OTIO Track.dir: {}".format(dir(otio_clip.parent()))) + track_name = otio_clip.parent().name + parent_range = otio_clip.range_in_parent() + if s_track_name not in track_name: + continue + if otio_clip.name not in segment.name.get_value(): + continue + if openpype.lib.is_overlapping_otio_ranges( + parent_range, timeline_range, strict=True): - # return hiero_export.create_otio_time_range( - # frame_start, frame_duration, fps) + # add pypedata marker to otio_clip metadata + for marker in otio_clip.markers: + if opfapi.MARKER_NAME in marker.name: + otio_clip.metadata.update(marker.metadata) + return {"otioClip": otio_clip} + + return None + + def _create_otio_time_range_from_timeline_item_data(self, clip_data): + frame_start = int(clip_data["record_in"]) + frame_duration = int(clip_data["record_duration"]) + + return flame_export.create_otio_time_range( + frame_start, frame_duration, self.fps) From 67138f2787bb871b79ab865e162e53a542414779 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Jan 2022 14:35:20 +0100 Subject: [PATCH 24/34] flame: fix correct search condition --- openpype/hosts/flame/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index e53127503b..b963a1cb39 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -559,7 +559,7 @@ def get_segment_attributes(segment): attr = getattr(segment, attr_name) segment_attrs_data[attr] = str(attr).replace("+", ":") - if attr in ["record_in", "record_out"]: + if attr_name in ["record_in", "record_out"]: clip_data[attr_name] = attr.relative_frame else: clip_data[attr_name] = attr.frame From f0a11fa0bfdf4ac33218c4154cd150cbe5a99490 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Jan 2022 15:37:11 +0100 Subject: [PATCH 25/34] flame: fix otio path padding --- openpype/hosts/flame/otio/flame_export.py | 2 +- openpype/hosts/flame/otio/utils.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index aea1f387e8..615904ec09 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -295,7 +295,7 @@ def create_otio_reference(clip_data): if is_sequence: metadata.update({ "isSequence": True, - "padding": padding + "padding": len(padding) }) otio_ex_ref_item = None diff --git a/openpype/hosts/flame/otio/utils.py b/openpype/hosts/flame/otio/utils.py index 229946343b..57a15d65a1 100644 --- a/openpype/hosts/flame/otio/utils.py +++ b/openpype/hosts/flame/otio/utils.py @@ -1,4 +1,5 @@ import re +import os import opentimelineio as otio import logging log = logging.getLogger(__name__) @@ -33,19 +34,21 @@ def get_reformated_path(path, padded=True): get_reformated_path("plate.1001.exr") > plate.%04d.exr """ - padding = get_padding_from_path(path) - found = get_frame_from_path(path) + basename = os.path.basename(path) + dirpath = os.path.dirname(path) + padding = get_padding_from_path(basename) + found = get_frame_from_path(basename) if not found: log.info("Path is not sequence: {}".format(path)) return path if padded: - path = path.replace(found, "%0{}d".format(padding)) + basename = basename.replace(found, "%0{}d".format(padding)) else: - path = path.replace(found, "%d") + basename = basename.replace(found, "%d") - return path + return os.path.join(dirpath, basename) def get_padding_from_path(path): From 10de030e133d7beca656515638da48312b33751f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Jan 2022 15:40:33 +0100 Subject: [PATCH 26/34] flame: adding host to global plugins --- openpype/plugins/publish/collect_hierarchy.py | 2 +- openpype/plugins/publish/collect_otio_frame_ranges.py | 2 +- openpype/plugins/publish/collect_otio_review.py | 2 +- openpype/plugins/publish/collect_otio_subset_resources.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/collect_hierarchy.py b/openpype/plugins/publish/collect_hierarchy.py index f7d1c6b4be..7f7306f73b 100644 --- a/openpype/plugins/publish/collect_hierarchy.py +++ b/openpype/plugins/publish/collect_hierarchy.py @@ -15,7 +15,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin): label = "Collect Hierarchy" order = pyblish.api.CollectorOrder - 0.47 families = ["shot"] - hosts = ["resolve", "hiero"] + hosts = ["resolve", "hiero", "flame"] def process(self, context): temp_context = {} diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index a35ef47e79..511ed757b3 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -20,7 +20,7 @@ class CollectOcioFrameRanges(pyblish.api.InstancePlugin): label = "Collect OTIO Frame Ranges" order = pyblish.api.CollectorOrder - 0.48 families = ["shot", "clip"] - hosts = ["resolve", "hiero"] + hosts = ["resolve", "hiero", "flame"] def process(self, instance): # get basic variables diff --git a/openpype/plugins/publish/collect_otio_review.py b/openpype/plugins/publish/collect_otio_review.py index 10ceafdcca..6634be0671 100644 --- a/openpype/plugins/publish/collect_otio_review.py +++ b/openpype/plugins/publish/collect_otio_review.py @@ -22,7 +22,7 @@ class CollectOcioReview(pyblish.api.InstancePlugin): label = "Collect OTIO Review" order = pyblish.api.CollectorOrder - 0.47 families = ["clip"] - hosts = ["resolve", "hiero"] + hosts = ["resolve", "hiero", "flame"] def process(self, instance): # get basic variables diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 571d0d56a4..d740ceb508 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -20,7 +20,7 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin): label = "Collect OTIO Subset Resources" order = pyblish.api.CollectorOrder - 0.47 families = ["clip"] - hosts = ["resolve", "hiero"] + hosts = ["resolve", "hiero", "flame"] def process(self, instance): From 35f721fc8ac41de47473f93eeb8a650dfa76c8a0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Jan 2022 16:08:09 +0100 Subject: [PATCH 27/34] flame: adding file path to instance data --- .../hosts/flame/plugins/publish/precollect_instances.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/precollect_instances.py index fa007b3efd..a093bb82fa 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_instances.py +++ b/openpype/hosts/flame/plugins/publish/precollect_instances.py @@ -91,7 +91,8 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "publish": marker_data["publish"], "fps": self.fps, "flameSourceClip": source_clip, - "sourceFirstFrame": first_frame + "sourceFirstFrame": first_frame, + "path": file_path }) # otio clip data @@ -228,17 +229,11 @@ class PrecollectInstances(pyblish.api.ContextPlugin): """ segment = clip_data["PySegment"] - - self.log.debug( - ">> flame Track.dir: {}".format(dir(segment.parent))) s_track_name = segment.parent.name.get_value() - timeline_range = self._create_otio_time_range_from_timeline_item_data( clip_data) for otio_clip in self.otio_timeline.each_clip(): - self.log.debug( - ">> OTIO Track.dir: {}".format(dir(otio_clip.parent()))) track_name = otio_clip.parent().name parent_range = otio_clip.range_in_parent() if s_track_name not in track_name: From 39578a4a5104737a5e5bbcaa44bd8eebc64cebe2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Jan 2022 16:11:19 +0100 Subject: [PATCH 28/34] flame: adding host to ftrack plugin --- .../ftrack/plugins/publish/integrate_hierarchy_ftrack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index fbd64d9f70..61892240d7 100644 --- a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -63,7 +63,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder - 0.04 label = 'Integrate Hierarchy To Ftrack' families = ["shot"] - hosts = ["hiero", "resolve", "standalonepublisher"] + hosts = ["hiero", "resolve", "standalonepublisher", "flame"] optional = False def process(self, context): From 226903ea0b45d7ff09e5dd284a80566d2b88402d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Jan 2022 20:24:06 +0100 Subject: [PATCH 29/34] hound: flake8 fix --- openpype/hosts/flame/plugins/publish/precollect_instances.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/precollect_instances.py index a093bb82fa..b4b2ebf63f 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_instances.py +++ b/openpype/hosts/flame/plugins/publish/precollect_instances.py @@ -32,7 +32,8 @@ class PrecollectInstances(pyblish.api.ContextPlugin): # get openpype tag data marker_data = opfapi.get_segment_data_marker(segment) - self.log.debug("__ marker_data: {}".format(pformat(marker_data))) + self.log.debug("__ marker_data: {}".format( + pformat(marker_data))) if not marker_data: continue From ff76b5c3d98375a62cd4a24cd38da82d92e9d73e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Jan 2022 14:23:54 +0100 Subject: [PATCH 30/34] flame: rename path to fname https://github.com/pypeclub/OpenPype/pull/2519#discussion_r785974817 --- openpype/hosts/flame/api/lib.py | 40 ++++++++++++++------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index b963a1cb39..1e444f3b40 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -601,12 +601,12 @@ def get_clips_in_reels(project): return output_clips -def get_reformated_path(path, padded=True): +def get_reformated_path(fname, padded=True): """ Return fixed python expression path Args: - path (str): path url or simple file name + fname (str): file name Returns: type: string with reformated path @@ -615,27 +615,27 @@ def get_reformated_path(path, padded=True): get_reformated_path("plate.1001.exr") > plate.%04d.exr """ - padding = get_padding_from_path(path) - found = get_frame_from_path(path) + padding = get_padding_from_path(fname) + found = get_frame_from_path(fname) if not found: - log.info("Path is not sequence: {}".format(path)) - return path + log.info("File name is not sequence: {}".format(fname)) + return fname if padded: - path = path.replace(found, "%0{}d".format(padding)) + fname = fname.replace(found, "%0{}d".format(padding)) else: - path = path.replace(found, "%d") + fname = fname.replace(found, "%d") - return path + return fname -def get_padding_from_path(path): +def get_padding_from_path(fname): """ Return padding number from Flame path style Args: - path (str): path url or simple file name + fname (str): file name Returns: int: padding number @@ -644,20 +644,17 @@ def get_padding_from_path(path): get_padding_from_path("plate.0001.exr") > 4 """ - found = get_frame_from_path(path) + found = get_frame_from_path(fname) - if found: - return len(found) - else: - return None + return len(found) if found else None -def get_frame_from_path(path): +def get_frame_from_path(fname): """ Return sequence number from Flame path style Args: - path (str): path url or simple file name + fname (str): file name Returns: int: sequence frame number @@ -669,9 +666,6 @@ def get_frame_from_path(path): """ frame_pattern = re.compile(r"[._](\d+)[.]") - found = re.findall(frame_pattern, path) + found = re.findall(frame_pattern, fname) - if found: - return found.pop() - else: - return None + return found.pop() if found else None From ffc92fd1f447588833bd230c8d17f766a52dae93 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Jan 2022 14:40:19 +0100 Subject: [PATCH 31/34] flame: subset name from settings https://github.com/pypeclub/OpenPype/pull/2519#discussion_r785985961 --- .../plugins/publish/precollect_workfile.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index aff85e22e6..8d49993576 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -1,5 +1,6 @@ import pyblish.api import avalon.api as avalon +import openpype.lib as oplib import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export @@ -11,19 +12,32 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.5 def process(self, context): - asset = avalon.Session["AVALON_ASSET"] - subset = "otioTimeline" + # plugin defined + family = "workfile" + variant = "otioTimeline" + + # main + asset_doc = context.data["assetEntity"] + task_name = avalon.Session["AVALON_TASK"] project = opfapi.get_current_project() sequence = opfapi.get_current_sequence(opfapi.CTX.selection) + # create subset name + subset_name = oplib.get_subset_name_with_asset_doc( + family, + variant, + task_name, + asset_doc, + ) + # adding otio timeline to context with opfapi.maintained_segment_selection(sequence): otio_timeline = flame_export.create_otio_timeline(sequence) instance_data = { - "name": "{}_{}".format(asset, subset), - "asset": asset, - "subset": "{}{}".format(asset, subset.capitalize()), + "name": subset_name, + "asset": asset_doc["name"], + "subset": subset_name, "family": "workfile" } From 07b9a769e1bbbbf5fc5271d2faa2eb2492eea861 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Jan 2022 14:46:49 +0100 Subject: [PATCH 32/34] flame: removing inherited mess https://github.com/pypeclub/OpenPype/pull/2519#discussion_r785987192 --- .../plugins/publish/precollect_instances.py | 30 ++++++++----------- .../plugins/publish/precollect_workfile.py | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/precollect_instances.py index b4b2ebf63f..2e5a0c406b 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_instances.py +++ b/openpype/hosts/flame/plugins/publish/precollect_instances.py @@ -10,7 +10,7 @@ from pprint import pformat class PrecollectInstances(pyblish.api.ContextPlugin): """Collect all Track items selection.""" - order = pyblish.api.CollectorOrder - 0.49 + order = pyblish.api.CollectorOrder - 0.47 label = "Precollect Instances" hosts = ["flame"] @@ -57,16 +57,10 @@ class PrecollectInstances(pyblish.api.ContextPlugin): marker_data["handleEnd"] = min( marker_data["handleEnd"], tail) - # add audio to families - with_audio = False - if marker_data.pop("audio"): - with_audio = True + with_audio = bool(marker_data.pop("audio")) - # add tag data to instance data - data = { - k: v for k, v in marker_data.items() - if k not in ("id", "applieswhole", "label") - } + # add marker data to instance data + inst_data = dict(marker_data.items()) asset = marker_data["asset"] subset = marker_data["subset"] @@ -83,7 +77,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): label += " {}".format(subset) label += " {}".format("[" + ", ".join(families) + "]") - data.update({ + inst_data.update({ "name": "{}_{}".format(asset, subset), "label": label, "asset": asset, @@ -96,17 +90,19 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "path": file_path }) - # otio clip data + # get otio clip data otio_data = self._get_otio_clip_instance_data(clip_data) or {} self.log.debug("__ otio_data: {}".format(pformat(otio_data))) - data.update(otio_data) - self.log.debug("__ data: {}".format(pformat(data))) + + # add to instance data + inst_data.update(otio_data) + self.log.debug("__ inst_data: {}".format(pformat(inst_data))) # add resolution - self._get_resolution_to_data(data, context) + self._get_resolution_to_data(inst_data, context) # create instance - instance = context.create_instance(**data) + instance = context.create_instance(**inst_data) # add colorspace data instance.data.update({ @@ -116,7 +112,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): }) # create shot instance for shot attributes create/update - self._create_shot_instance(context, clip_name, **data) + self._create_shot_instance(context, clip_name, **inst_data) self.log.info("Creating instance: {}".format(instance)) self.log.info( diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/precollect_workfile.py index 8d49993576..34bcab83a7 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/precollect_workfile.py @@ -9,7 +9,7 @@ class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): """Inject the current working context into publish context""" label = "Precollect Timeline OTIO" - order = pyblish.api.CollectorOrder - 0.5 + order = pyblish.api.CollectorOrder - 0.48 def process(self, context): # plugin defined From 33a7ddcf0b39897fa0b963af21267edffb5f6a57 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Jan 2022 20:13:32 +0100 Subject: [PATCH 33/34] flame: improving filename frame number operations --- openpype/hosts/flame/api/lib.py | 37 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 1e444f3b40..f102eba060 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -16,6 +16,7 @@ from openpype.api import Logger log = Logger.get_logger(__name__) +FRAME_PATTERN = re.compile(r"[\._](\d+)[\.]") class CTX: # singleton used for passing data between api modules @@ -601,12 +602,12 @@ def get_clips_in_reels(project): return output_clips -def get_reformated_path(fname, padded=True): +def get_reformated_path(filename, padded=True): """ Return fixed python expression path Args: - fname (str): file name + filename (str): file name Returns: type: string with reformated path @@ -615,27 +616,28 @@ def get_reformated_path(fname, padded=True): get_reformated_path("plate.1001.exr") > plate.%04d.exr """ - padding = get_padding_from_path(fname) - found = get_frame_from_path(fname) + found = FRAME_PATTERN.search(filename) if not found: - log.info("File name is not sequence: {}".format(fname)) - return fname + log.info("File name is not sequence: {}".format(filename)) + return filename - if padded: - fname = fname.replace(found, "%0{}d".format(padding)) - else: - fname = fname.replace(found, "%d") + padding = get_padding_from_path(filename) - return fname + replacement = "%0{}d".format(padding) if padded else "%d" + start_idx, end_idx = found.span(1) + + return replacement.join( + [filename[:start_idx], filename[end_idx:]] + ) -def get_padding_from_path(fname): +def get_padding_from_path(filename): """ Return padding number from Flame path style Args: - fname (str): file name + filename (str): file name Returns: int: padding number @@ -644,17 +646,17 @@ def get_padding_from_path(fname): get_padding_from_path("plate.0001.exr") > 4 """ - found = get_frame_from_path(fname) + found = get_frame_from_path(filename) return len(found) if found else None -def get_frame_from_path(fname): +def get_frame_from_path(filename): """ Return sequence number from Flame path style Args: - fname (str): file name + filename (str): file name Returns: int: sequence frame number @@ -664,8 +666,7 @@ def get_frame_from_path(fname): ("plate.0001.exr") > 0001 """ - frame_pattern = re.compile(r"[._](\d+)[.]") - found = re.findall(frame_pattern, fname) + found = re.findall(FRAME_PATTERN, filename) return found.pop() if found else None From bef26229e5cd3a9b85955ac4fc108bba3fc45ed7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 19 Jan 2022 20:32:54 +0100 Subject: [PATCH 34/34] flame: renaming collectors --- ...collect_instances.py => collect_timeline_instances.py} | 8 ++++---- .../{precollect_workfile.py => collect_timeline_otio.py} | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) rename openpype/hosts/flame/plugins/publish/{precollect_instances.py => collect_timeline_instances.py} (97%) rename openpype/hosts/flame/plugins/publish/{precollect_workfile.py => collect_timeline_otio.py} (92%) diff --git a/openpype/hosts/flame/plugins/publish/precollect_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py similarity index 97% rename from openpype/hosts/flame/plugins/publish/precollect_instances.py rename to openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 2e5a0c406b..a223a17977 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -7,11 +7,11 @@ from openpype.hosts.flame.otio import flame_export from pprint import pformat -class PrecollectInstances(pyblish.api.ContextPlugin): - """Collect all Track items selection.""" +class CollectTimelineInstances(pyblish.api.ContextPlugin): + """Collect all Timeline segment selection.""" - order = pyblish.api.CollectorOrder - 0.47 - label = "Precollect Instances" + order = pyblish.api.CollectorOrder - 0.09 + label = "Collect timeline Instances" hosts = ["flame"] audio_track_items = [] diff --git a/openpype/hosts/flame/plugins/publish/precollect_workfile.py b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py similarity index 92% rename from openpype/hosts/flame/plugins/publish/precollect_workfile.py rename to openpype/hosts/flame/plugins/publish/collect_timeline_otio.py index 34bcab83a7..faa5be9d68 100644 --- a/openpype/hosts/flame/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_otio.py @@ -5,11 +5,11 @@ import openpype.hosts.flame.api as opfapi from openpype.hosts.flame.otio import flame_export -class PrecollecTimelineOCIO(pyblish.api.ContextPlugin): +class CollecTimelineOTIO(pyblish.api.ContextPlugin): """Inject the current working context into publish context""" - label = "Precollect Timeline OTIO" - order = pyblish.api.CollectorOrder - 0.48 + label = "Collect Timeline OTIO" + order = pyblish.api.CollectorOrder - 0.099 def process(self, context): # plugin defined