diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index 56bbadd2fc..f210c27f87 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -68,7 +68,8 @@ from .workio import ( ) from .render_utils import ( export_clip, - get_preset_path_by_xml_name + get_preset_path_by_xml_name, + modify_preset_file ) __all__ = [ @@ -140,5 +141,6 @@ __all__ = [ # render utils "export_clip", - "get_preset_path_by_xml_name" + "get_preset_path_by_xml_name", + "modify_preset_file" ] diff --git a/openpype/hosts/flame/api/render_utils.py b/openpype/hosts/flame/api/render_utils.py index 1b086646cc..473fb2f985 100644 --- a/openpype/hosts/flame/api/render_utils.py +++ b/openpype/hosts/flame/api/render_utils.py @@ -1,4 +1,5 @@ import os +from xml.etree import ElementTree as ET def export_clip(export_path, clip, preset_path, **kwargs): @@ -123,3 +124,29 @@ def get_preset_path_by_xml_name(xml_preset_name): # if nothing found then return False return False + + +def modify_preset_file(xml_path, staging_dir, data): + """Modify xml preset with input data + + Args: + xml_path (str ): path for input xml preset + staging_dir (str): staging dir path + data (dict): data where key is xmlTag and value as string + + Returns: + str: _description_ + """ + # create temp path + dirname, basename = os.path.split(xml_path) + temp_path = os.path.join(staging_dir, basename) + + # change xml following data keys + with open(xml_path, "r") as datafile: + tree = ET.parse(datafile) + for key, value in data.items(): + for element in tree.findall(".//{}".format(key)): + element.text = str(value) + tree.write(temp_path) + + return temp_path diff --git a/openpype/hosts/flame/api/scripts/wiretap_com.py b/openpype/hosts/flame/api/scripts/wiretap_com.py index c864399608..ee906c2608 100644 --- a/openpype/hosts/flame/api/scripts/wiretap_com.py +++ b/openpype/hosts/flame/api/scripts/wiretap_com.py @@ -420,13 +420,20 @@ class WireTapCom(object): RuntimeError: Not able to set colorspace policy """ color_policy = color_policy or "Legacy" + + # check if the colour policy in custom dir + if not os.path.exists(color_policy): + color_policy = "/syncolor/policies/Autodesk/{}".format( + color_policy) + + # create arguments project_colorspace_cmd = [ os.path.join( self.wiretap_tools_dir, "wiretap_duplicate_node" ), "-s", - "/syncolor/policies/Autodesk/{}".format(color_policy), + color_policy, "-n", "/projects/{}/syncolor".format(project_name) ] diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 0d63b0d926..ad2b0dc897 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -73,7 +73,7 @@ class FlamePrelaunch(PreLaunchHook): "FrameWidth": int(width), "FrameHeight": int(height), "AspectRatio": float((width / height) * _db_p_data["pixelAspect"]), - "FrameRate": "{} fps".format(fps), + "FrameRate": self._get_flame_fps(fps), "FrameDepth": str(imageio_flame["project"]["frameDepth"]), "FieldDominance": str(imageio_flame["project"]["fieldDominance"]) } @@ -101,6 +101,28 @@ class FlamePrelaunch(PreLaunchHook): self.launch_context.launch_args.extend(app_arguments) + def _get_flame_fps(self, fps_num): + fps_table = { + float(23.976): "23.976 fps", + int(25): "25 fps", + int(24): "24 fps", + float(29.97): "29.97 fps DF", + int(30): "30 fps", + int(50): "50 fps", + float(59.94): "59.94 fps DF", + int(60): "60 fps" + } + + match_key = min(fps_table.keys(), key=lambda x: abs(x - fps_num)) + + try: + return fps_table[match_key] + except KeyError as msg: + raise KeyError(( + "Missing FPS key in conversion table. " + "Following keys are available: {}".format(fps_table.keys()) + )) from msg + def _add_pythonpath(self): pythonpath = self.launch_context.env.get("PYTHONPATH") diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index db85bede85..3b1466925f 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -22,6 +22,7 @@ class ExtractSubsetResources(openpype.api.Extractor): "ext": "jpg", "xml_preset_file": "Jpeg (8-bit).xml", "xml_preset_dir": "", + "export_type": "File Sequence", "colorspace_out": "Output - sRGB", "representation_add_range": False, "representation_tags": ["thumbnail"] @@ -30,6 +31,7 @@ class ExtractSubsetResources(openpype.api.Extractor): "ext": "mov", "xml_preset_file": "Apple iPad (1920x1080).xml", "xml_preset_dir": "", + "export_type": "Movie", "colorspace_out": "Output - Rec.709", "representation_add_range": True, "representation_tags": [ @@ -54,21 +56,35 @@ class ExtractSubsetResources(openpype.api.Extractor): ): instance.data["representations"] = [] - frame_start = instance.data["frameStart"] - handle_start = instance.data["handleStart"] - frame_start_handle = frame_start - handle_start - source_first_frame = instance.data["sourceFirstFrame"] - source_start_handles = instance.data["sourceStartH"] - source_end_handles = instance.data["sourceEndH"] - source_duration_handles = ( - source_end_handles - source_start_handles) + 1 - + # flame objects + segment = instance.data["item"] + sequence_clip = instance.context.data["flameSequence"] clip_data = instance.data["flameSourceClip"] clip = clip_data["PyClip"] - in_mark = (source_start_handles - source_first_frame) + 1 - out_mark = in_mark + source_duration_handles + # segment's parent track name + s_track_name = segment.parent.name.get_value() + # get configured workfile frame start/end (handles excluded) + frame_start = instance.data["frameStart"] + # get media source first frame + source_first_frame = instance.data["sourceFirstFrame"] + + # get timeline in/out of segment + clip_in = instance.data["clipIn"] + clip_out = instance.data["clipOut"] + + # get handles value - take only the max from both + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleStart"] + handles = max(handle_start, handle_end) + + # get media source range with handles + source_end_handles = instance.data["sourceEndH"] + source_start_handles = instance.data["sourceStartH"] + source_end_handles = instance.data["sourceEndH"] + + # create staging dir path staging_dir = self.staging_dir(instance) # add default preset type for thumbnail and reviewable video @@ -77,15 +93,52 @@ class ExtractSubsetResources(openpype.api.Extractor): export_presets = deepcopy(self.default_presets) export_presets.update(self.export_presets_mapping) - # with maintained duplication loop all presets - with opfapi.maintained_object_duplication(clip) as duplclip: - # loop all preset names and - for unique_name, preset_config in export_presets.items(): + # loop all preset names and + for unique_name, preset_config in export_presets.items(): + modify_xml_data = {} + + # get all presets attributes + preset_file = preset_config["xml_preset_file"] + preset_dir = preset_config["xml_preset_dir"] + export_type = preset_config["export_type"] + repre_tags = preset_config["representation_tags"] + color_out = preset_config["colorspace_out"] + + # get frame range with handles for representation range + frame_start_handle = frame_start - handle_start + source_duration_handles = ( + source_end_handles - source_start_handles) + 1 + + # define in/out marks + in_mark = (source_start_handles - source_first_frame) + 1 + out_mark = in_mark + source_duration_handles + + # by default export source clips + exporting_clip = clip + + if export_type == "Sequence Publish": + # change export clip to sequence + exporting_clip = sequence_clip + + # change in/out marks to timeline in/out + in_mark = clip_in + out_mark = clip_out + + # add xml tags modifications + modify_xml_data.update({ + "exportHandles": True, + "nbHandles": handles, + "startFrame": frame_start + }) + + # with maintained duplication loop all presets + with opfapi.maintained_object_duplication( + exporting_clip) as duplclip: kwargs = {} - preset_file = preset_config["xml_preset_file"] - preset_dir = preset_config["xml_preset_dir"] - repre_tags = preset_config["representation_tags"] - color_out = preset_config["colorspace_out"] + + if export_type == "Sequence Publish": + # only keep visible layer where instance segment is child + self.hide_other_tracks(duplclip, s_track_name) # validate xml preset file is filled if preset_file == "": @@ -108,10 +161,13 @@ class ExtractSubsetResources(openpype.api.Extractor): ) # create preset path - preset_path = str(os.path.join( + preset_orig_xml_path = str(os.path.join( preset_dir, preset_file )) + preset_path = opfapi.modify_preset_file( + preset_orig_xml_path, staging_dir, modify_xml_data) + # define kwargs based on preset type if "thumbnail" in unique_name: kwargs["thumb_frame_number"] = in_mark + ( @@ -122,6 +178,7 @@ class ExtractSubsetResources(openpype.api.Extractor): "out_mark": out_mark }) + # get and make export dir paths export_dir_path = str(os.path.join( staging_dir, unique_name )) @@ -132,6 +189,7 @@ class ExtractSubsetResources(openpype.api.Extractor): export_dir_path, duplclip, preset_path, **kwargs) extension = preset_config["ext"] + # create representation data representation_data = { "name": unique_name, @@ -159,7 +217,12 @@ class ExtractSubsetResources(openpype.api.Extractor): # add files to represetation but add # imagesequence as list if ( - "movie_file" in preset_path + # first check if path in files is not mov extension + [ + f for f in files + if os.path.splitext(f)[-1] == ".mov" + ] + # then try if thumbnail is not in unique name or unique_name == "thumbnail" ): representation_data["files"] = files.pop() @@ -246,3 +309,19 @@ class ExtractSubsetResources(openpype.api.Extractor): ) return new_stage_dir, new_files_list + + def hide_other_tracks(self, sequence_clip, track_name): + """Helper method used only if sequence clip is used + + Args: + sequence_clip (flame.Clip): sequence clip + track_name (str): track name + """ + # create otio tracks and clips + for ver in sequence_clip.versions: + for track in ver.tracks: + if len(track.segments) == 0 and track.hidden: + continue + + if track.name.get_value() != track_name: + track.hidden = True diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index 6fb6f55528..ef9c2b1041 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -27,6 +27,7 @@ "ext": "exr", "xml_preset_file": "OpenEXR (16-bit fp DWAA).xml", "xml_preset_dir": "", + "export_type": "File Sequence", "colorspace_out": "ACES - ACEScg", "representation_add_range": true, "representation_tags": [] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index dc88d11f61..1f30b45981 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -171,6 +171,24 @@ "label": "XML preset folder (optional)", "type": "text" }, + { + "key": "export_type", + "label": "Eport clip type", + "type": "enum", + "default": "File Sequence", + "enum_items": [ + { + "Movie": "Movie" + }, + { + "File Sequence": "File Sequence" + }, + { + "Sequence Publish": "Sequence Publish" + } + ] + + }, { "key": "colorspace_out", "label": "Output color (imageio)", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json index 3bec19c3d0..2867332d82 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json @@ -457,7 +457,7 @@ { "type": "text", "key": "colourPolicy", - "label": "Colour Policy" + "label": "Colour Policy (name or path)" }, { "type": "text",