diff --git a/server_addon/flame/client/ayon_flame/__init__.py b/server_addon/flame/client/ayon_flame/__init__.py deleted file mode 100644 index d2d89bdb01..0000000000 --- a/server_addon/flame/client/ayon_flame/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .version import __version__ -from .addon import ( - FLAME_ADDON_ROOT, - FlameAddon, -) - - -__all__ = ( - "__version__", - - "FLAME_ADDON_ROOT", - "FlameAddon", -) diff --git a/server_addon/flame/client/ayon_flame/addon.py b/server_addon/flame/client/ayon_flame/addon.py deleted file mode 100644 index 5a96a9332e..0000000000 --- a/server_addon/flame/client/ayon_flame/addon.py +++ /dev/null @@ -1,35 +0,0 @@ -import os -from ayon_core.addon import AYONAddon, IHostAddon - -from .version import __version__ - -FLAME_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__)) - - -class FlameAddon(AYONAddon, IHostAddon): - name = "flame" - version = __version__ - host_name = "flame" - - def add_implementation_envs(self, env, _app): - # Add requirements to DL_PYTHON_HOOK_PATH - env["DL_PYTHON_HOOK_PATH"] = os.path.join(FLAME_ADDON_ROOT, "startup") - env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None) - - # Set default values if are not already set via settings - defaults = { - "LOGLEVEL": "DEBUG" - } - for key, value in defaults.items(): - if not env.get(key): - env[key] = value - - def get_launch_hook_paths(self, app): - if app.host_name != self.host_name: - return [] - return [ - os.path.join(FLAME_ADDON_ROOT, "hooks") - ] - - def get_workfile_extensions(self): - return [".otoc"] diff --git a/server_addon/flame/client/ayon_flame/api/__init__.py b/server_addon/flame/client/ayon_flame/api/__init__.py deleted file mode 100644 index 8fcf0c92b0..0000000000 --- a/server_addon/flame/client/ayon_flame/api/__init__.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -AYON Autodesk Flame api -""" -from .constants import ( - COLOR_MAP, - MARKER_NAME, - MARKER_COLOR, - MARKER_DURATION, - MARKER_PUBLISH_DEFAULT -) -from .lib import ( - CTX, - FlameAppFramework, - get_current_project, - get_current_sequence, - create_segment_data_marker, - get_segment_data_marker, - set_segment_data_marker, - set_publish_attribute, - get_publish_attribute, - get_sequence_segments, - maintained_segment_selection, - reset_segment_selection, - get_segment_attributes, - get_clips_in_reels, - get_reformatted_filename, - get_frame_from_filename, - get_padding_from_filename, - maintained_object_duplication, - maintained_temp_file_path, - get_clip_segment, - get_batch_group_from_desktop, - MediaInfoFile, - TimeEffectMetadata -) -from .utils import ( - setup, - get_flame_version, - get_flame_install_root -) -from .pipeline import ( - install, - uninstall, - ls, - containerise, - update_container, - remove_instance, - list_instances, - imprint, - maintained_selection -) -from .menu import ( - FlameMenuProjectConnect, - FlameMenuTimeline, - FlameMenuUniversal -) -from .plugin import ( - Creator, - PublishableClip, - ClipLoader, - OpenClipSolver -) -from .workio import ( - open_file, - save_file, - current_file, - has_unsaved_changes, - file_extensions, - work_root -) -from .render_utils import ( - export_clip, - get_preset_path_by_xml_name, - modify_preset_file -) -from .batch_utils import ( - create_batch_group, - create_batch_group_conent -) - -__all__ = [ - # constants - "COLOR_MAP", - "MARKER_NAME", - "MARKER_COLOR", - "MARKER_DURATION", - "MARKER_PUBLISH_DEFAULT", - - # lib - "CTX", - "FlameAppFramework", - "get_current_project", - "get_current_sequence", - "create_segment_data_marker", - "get_segment_data_marker", - "set_segment_data_marker", - "set_publish_attribute", - "get_publish_attribute", - "get_sequence_segments", - "maintained_segment_selection", - "reset_segment_selection", - "get_segment_attributes", - "get_clips_in_reels", - "get_reformatted_filename", - "get_frame_from_filename", - "get_padding_from_filename", - "maintained_object_duplication", - "maintained_temp_file_path", - "get_clip_segment", - "get_batch_group_from_desktop", - "MediaInfoFile", - "TimeEffectMetadata", - - # pipeline - "install", - "uninstall", - "ls", - "containerise", - "update_container", - "reload_pipeline", - "maintained_selection", - "remove_instance", - "list_instances", - "imprint", - "maintained_selection", - - # utils - "setup", - "get_flame_version", - "get_flame_install_root", - - # menu - "FlameMenuProjectConnect", - "FlameMenuTimeline", - "FlameMenuUniversal", - - # plugin - "Creator", - "PublishableClip", - "ClipLoader", - "OpenClipSolver", - - # workio - "open_file", - "save_file", - "current_file", - "has_unsaved_changes", - "file_extensions", - "work_root", - - # render utils - "export_clip", - "get_preset_path_by_xml_name", - "modify_preset_file", - - # batch utils - "create_batch_group", - "create_batch_group_conent" -] diff --git a/server_addon/flame/client/ayon_flame/api/batch_utils.py b/server_addon/flame/client/ayon_flame/api/batch_utils.py deleted file mode 100644 index 9d419a4a90..0000000000 --- a/server_addon/flame/client/ayon_flame/api/batch_utils.py +++ /dev/null @@ -1,151 +0,0 @@ -import flame - - -def create_batch_group( - name, - frame_start, - frame_duration, - update_batch_group=None, - **kwargs -): - """Create Batch Group in active project's Desktop - - Args: - name (str): name of batch group to be created - frame_start (int): start frame of batch - frame_end (int): end frame of batch - update_batch_group (PyBatch)[optional]: batch group to update - - Return: - PyBatch: active flame batch group - """ - # make sure some batch obj is present - batch_group = update_batch_group or flame.batch - - schematic_reels = kwargs.get("shematic_reels") or ['LoadedReel1'] - shelf_reels = kwargs.get("shelf_reels") or ['ShelfReel1'] - - handle_start = kwargs.get("handleStart") or 0 - handle_end = kwargs.get("handleEnd") or 0 - - frame_start -= handle_start - frame_duration += handle_start + handle_end - - if not update_batch_group: - # Create batch group with name, start_frame value, duration value, - # set of schematic reel names, set of shelf reel names - batch_group = batch_group.create_batch_group( - name, - start_frame=frame_start, - duration=frame_duration, - reels=schematic_reels, - shelf_reels=shelf_reels - ) - else: - batch_group.name = name - batch_group.start_frame = frame_start - batch_group.duration = frame_duration - - # add reels to batch group - _add_reels_to_batch_group( - batch_group, schematic_reels, shelf_reels) - - # TODO: also update write node if there is any - # TODO: also update loaders to start from correct frameStart - - if kwargs.get("switch_batch_tab"): - # use this command to switch to the batch tab - batch_group.go_to() - - return batch_group - - -def _add_reels_to_batch_group(batch_group, reels, shelf_reels): - # update or create defined reels - # helper variables - reel_names = [ - r.name.get_value() - for r in batch_group.reels - ] - shelf_reel_names = [ - r.name.get_value() - for r in batch_group.shelf_reels - ] - # add schematic reels - for _r in reels: - if _r in reel_names: - continue - batch_group.create_reel(_r) - - # add shelf reels - for _sr in shelf_reels: - if _sr in shelf_reel_names: - continue - batch_group.create_shelf_reel(_sr) - - -def create_batch_group_conent(batch_nodes, batch_links, batch_group=None): - """Creating batch group with links - - Args: - batch_nodes (list of dict): each dict is node definition - batch_links (list of dict): each dict is link definition - batch_group (PyBatch, optional): batch group. Defaults to None. - - Return: - dict: all batch nodes {name or id: PyNode} - """ - # make sure some batch obj is present - batch_group = batch_group or flame.batch - all_batch_nodes = { - b.name.get_value(): b - for b in batch_group.nodes - } - for node in batch_nodes: - # NOTE: node_props needs to be ideally OrederDict type - node_id, node_type, node_props = ( - node["id"], node["type"], node["properties"]) - - # get node name for checking if exists - node_name = node_props.pop("name", None) or node_id - - if all_batch_nodes.get(node_name): - # update existing batch node - batch_node = all_batch_nodes[node_name] - else: - # create new batch node - batch_node = batch_group.create_node(node_type) - - # set name - batch_node.name.set_value(node_name) - - # set attributes found in node props - for key, value in node_props.items(): - if not hasattr(batch_node, key): - continue - setattr(batch_node, key, value) - - # add created node for possible linking - all_batch_nodes[node_id] = batch_node - - # link nodes to each other - for link in batch_links: - _from_n, _to_n = link["from_node"], link["to_node"] - - # check if all linking nodes are available - if not all([ - all_batch_nodes.get(_from_n["id"]), - all_batch_nodes.get(_to_n["id"]) - ]): - continue - - # link nodes in defined link - batch_group.connect_nodes( - all_batch_nodes[_from_n["id"]], _from_n["connector"], - all_batch_nodes[_to_n["id"]], _to_n["connector"] - ) - - # sort batch nodes - batch_group.organize() - - return all_batch_nodes diff --git a/server_addon/flame/client/ayon_flame/api/constants.py b/server_addon/flame/client/ayon_flame/api/constants.py deleted file mode 100644 index 04191c539d..0000000000 --- a/server_addon/flame/client/ayon_flame/api/constants.py +++ /dev/null @@ -1,24 +0,0 @@ - -""" -AYON Flame api constances -""" -# AYON marker workflow variables -MARKER_NAME = "OpenPypeData" -MARKER_DURATION = 0 -MARKER_COLOR = "cyan" -MARKER_PUBLISH_DEFAULT = False - -# AYON color definitions -COLOR_MAP = { - "red": (1.0, 0.0, 0.0), - "orange": (1.0, 0.5, 0.0), - "yellow": (1.0, 1.0, 0.0), - "pink": (1.0, 0.5, 1.0), - "white": (1.0, 1.0, 1.0), - "green": (0.0, 1.0, 0.0), - "cyan": (0.0, 1.0, 1.0), - "blue": (0.0, 0.0, 1.0), - "purple": (0.5, 0.0, 0.5), - "magenta": (0.5, 0.0, 1.0), - "black": (0.0, 0.0, 0.0) -} diff --git a/server_addon/flame/client/ayon_flame/api/lib.py b/server_addon/flame/client/ayon_flame/api/lib.py deleted file mode 100644 index 8bfe6348ea..0000000000 --- a/server_addon/flame/client/ayon_flame/api/lib.py +++ /dev/null @@ -1,1272 +0,0 @@ -import sys -import os -import re -import json -import pickle -import clique -import tempfile -import traceback -import itertools -import contextlib -import xml.etree.cElementTree as cET -from copy import deepcopy, copy -from xml.etree import ElementTree as ET -from pprint import pformat - -from ayon_core.lib import Logger, run_subprocess - -from .constants import ( - MARKER_COLOR, - MARKER_DURATION, - MARKER_NAME, - COLOR_MAP, - MARKER_PUBLISH_DEFAULT -) - -log = Logger.get_logger(__name__) - -FRAME_PATTERN = re.compile(r"[\._](\d+)[\.]") - - -class CTX: - # singleton used for passing data between api modules - app_framework = None - flame_apps = [] - selection = None - - -@contextlib.contextmanager -def io_preferences_file(klass, filepath, write=False): - try: - flag = "w" if write else "r" - yield open(filepath, flag) - - except IOError as _error: - klass.log.info("Unable to work with preferences `{}`: {}".format( - filepath, _error)) - - -class FlameAppFramework(object): - # flameAppFramework class takes care of preferences - - class prefs_dict(dict): - - def __init__(self, master, name, **kwargs): - self.name = name - self.master = master - if not self.master.get(self.name): - self.master[self.name] = {} - self.master[self.name].__init__() - - def __getitem__(self, k): - return self.master[self.name].__getitem__(k) - - def __setitem__(self, k, v): - return self.master[self.name].__setitem__(k, v) - - def __delitem__(self, k): - return self.master[self.name].__delitem__(k) - - def get(self, k, default=None): - return self.master[self.name].get(k, default) - - def setdefault(self, k, default=None): - return self.master[self.name].setdefault(k, default) - - def pop(self, *args, **kwargs): - return self.master[self.name].pop(*args, **kwargs) - - def update(self, mapping=(), **kwargs): - self.master[self.name].update(mapping, **kwargs) - - def __contains__(self, k): - return self.master[self.name].__contains__(k) - - def copy(self): # don"t delegate w/ super - dict.copy() -> dict :( - return type(self)(self) - - def keys(self): - return self.master[self.name].keys() - - @classmethod - def fromkeys(cls, keys, v=None): - return cls.master[cls.name].fromkeys(keys, v) - - def __repr__(self): - return "{0}({1})".format( - type(self).__name__, self.master[self.name].__repr__()) - - def master_keys(self): - return self.master.keys() - - def __init__(self): - self.name = self.__class__.__name__ - self.bundle_name = "OpenPypeFlame" - # self.prefs scope is limited to flame project and user - self.prefs = {} - self.prefs_user = {} - self.prefs_global = {} - self.log = log - - try: - import flame - self.flame = flame - self.flame_project_name = self.flame.project.current_project.name - self.flame_user_name = flame.users.current_user.name - except Exception: - self.flame = None - self.flame_project_name = None - self.flame_user_name = None - - import socket - self.hostname = socket.gethostname() - - if sys.platform == "darwin": - self.prefs_folder = os.path.join( - os.path.expanduser("~"), - "Library", - "Caches", - "OpenPype", - self.bundle_name - ) - elif sys.platform.startswith("linux"): - self.prefs_folder = os.path.join( - os.path.expanduser("~"), - ".OpenPype", - self.bundle_name) - - self.prefs_folder = os.path.join( - self.prefs_folder, - self.hostname, - ) - - self.log.info("[{}] waking up".format(self.__class__.__name__)) - - try: - self.load_prefs() - except RuntimeError: - self.save_prefs() - - # menu auto-refresh defaults - if not self.prefs_global.get("menu_auto_refresh"): - self.prefs_global["menu_auto_refresh"] = { - "media_panel": True, - "batch": True, - "main_menu": True, - "timeline_menu": True - } - - self.apps = [] - - def get_pref_file_paths(self): - - prefix = self.prefs_folder + os.path.sep + self.bundle_name - prefs_file_path = "_".join([ - prefix, self.flame_user_name, - self.flame_project_name]) + ".prefs" - prefs_user_file_path = "_".join([ - prefix, self.flame_user_name]) + ".prefs" - prefs_global_file_path = prefix + ".prefs" - - return (prefs_file_path, prefs_user_file_path, prefs_global_file_path) - - def load_prefs(self): - - (proj_pref_path, user_pref_path, - glob_pref_path) = self.get_pref_file_paths() - - with io_preferences_file(self, proj_pref_path) as prefs_file: - self.prefs = pickle.load(prefs_file) - self.log.info( - "Project - preferences contents:\n{}".format( - pformat(self.prefs) - )) - - with io_preferences_file(self, user_pref_path) as prefs_file: - self.prefs_user = pickle.load(prefs_file) - self.log.info( - "User - preferences contents:\n{}".format( - pformat(self.prefs_user) - )) - - with io_preferences_file(self, glob_pref_path) as prefs_file: - self.prefs_global = pickle.load(prefs_file) - self.log.info( - "Global - preferences contents:\n{}".format( - pformat(self.prefs_global) - )) - - return True - - def save_prefs(self): - # make sure the preference folder is available - if not os.path.isdir(self.prefs_folder): - try: - os.makedirs(self.prefs_folder) - except Exception: - self.log.info("Unable to create folder {}".format( - self.prefs_folder)) - return False - - # get all pref file paths - (proj_pref_path, user_pref_path, - glob_pref_path) = self.get_pref_file_paths() - - with io_preferences_file(self, proj_pref_path, True) as prefs_file: - pickle.dump(self.prefs, prefs_file) - self.log.info( - "Project - preferences contents:\n{}".format( - pformat(self.prefs) - )) - - with io_preferences_file(self, user_pref_path, True) as prefs_file: - pickle.dump(self.prefs_user, prefs_file) - self.log.info( - "User - preferences contents:\n{}".format( - pformat(self.prefs_user) - )) - - with io_preferences_file(self, glob_pref_path, True) as prefs_file: - pickle.dump(self.prefs_global, prefs_file) - self.log.info( - "Global - preferences contents:\n{}".format( - pformat(self.prefs_global) - )) - - return True - - -def get_current_project(): - import flame - return flame.project.current_project - - -def get_current_sequence(selection): - import flame - - def segment_to_sequence(_segment): - track = _segment.parent - version = track.parent - return version.parent - - process_timeline = None - - if len(selection) == 1: - if isinstance(selection[0], flame.PySequence): - process_timeline = selection[0] - if isinstance(selection[0], flame.PySegment): - process_timeline = segment_to_sequence(selection[0]) - else: - for segment in selection: - if isinstance(segment, flame.PySegment): - process_timeline = segment_to_sequence(segment) - break - - return process_timeline - - -def rescan_hooks(): - import flame - try: - flame.execute_shortcut("Rescan Python Hooks") - except Exception: - pass - - -def get_metadata(project_name, _log=None): - # TODO: can be replaced by MediaInfoFile class method - from adsk.libwiretapPythonClientAPI import ( - WireTapClient, - WireTapServerHandle, - WireTapNodeHandle, - WireTapStr - ) - - class GetProjectColorPolicy(object): - def __init__(self, host_name=None, _log=None): - # Create a connection to the Backburner manager using the Wiretap - # python API. - # - self.log = _log or log - self.host_name = host_name or "localhost" - self._wiretap_client = WireTapClient() - if not self._wiretap_client.init(): - raise Exception("Could not initialize Wiretap Client") - self._server = WireTapServerHandle( - "{}:IFFFS".format(self.host_name)) - - def process(self, project_name): - policy_node_handle = WireTapNodeHandle( - self._server, - "/projects/{}/syncolor/policy".format(project_name) - ) - self.log.info(policy_node_handle) - - policy = WireTapStr() - if not policy_node_handle.getNodeTypeStr(policy): - self.log.warning( - "Could not retrieve policy of '%s': %s" % ( - policy_node_handle.getNodeId().id(), - policy_node_handle.lastError() - ) - ) - - return policy.c_str() - - policy_wiretap = GetProjectColorPolicy(_log=_log) - return policy_wiretap.process(project_name) - - -def get_segment_data_marker(segment, with_marker=None): - """ - Get openpype track item tag created by creator or loader plugin. - - Attributes: - segment (flame.PySegment): flame api object - with_marker (bool)[optional]: if true it will return also marker object - - Returns: - dict: openpype tag data - - Returns(with_marker=True): - flame.PyMarker, dict - """ - for marker in segment.markers: - comment = marker.comment.get_value() - color = marker.colour.get_value() - name = marker.name.get_value() - - if (name == MARKER_NAME) and ( - color == COLOR_MAP[MARKER_COLOR]): - if not with_marker: - return json.loads(comment) - else: - return marker, json.loads(comment) - - -def set_segment_data_marker(segment, data=None): - """ - Set openpype track item tag to input segment. - - Attributes: - segment (flame.PySegment): flame api object - - Returns: - dict: json loaded data - """ - data = data or dict() - - marker_data = get_segment_data_marker(segment, True) - - if marker_data: - # get available openpype tag if any - marker, tag_data = marker_data - # update tag data with new data - tag_data.update(data) - # update marker with tag data - marker.comment = json.dumps(tag_data) - else: - # update tag data with new data - marker = create_segment_data_marker(segment) - # add tag data to marker's comment - marker.comment = json.dumps(data) - - -def set_publish_attribute(segment, value): - """ Set Publish attribute in input Tag object - - Attribute: - segment (flame.PySegment)): flame api object - value (bool): True or False - """ - tag_data = get_segment_data_marker(segment) - tag_data["publish"] = value - - # set data to the publish attribute - set_segment_data_marker(segment, tag_data) - - -def get_publish_attribute(segment): - """ Get Publish attribute from input Tag object - - Attribute: - segment (flame.PySegment)): flame api object - - Returns: - bool: True or False - """ - tag_data = get_segment_data_marker(segment) - - if not tag_data: - set_publish_attribute(segment, MARKER_PUBLISH_DEFAULT) - return MARKER_PUBLISH_DEFAULT - - return tag_data["publish"] - - -def create_segment_data_marker(segment): - """ Create openpype marker on a segment. - - Attributes: - segment (flame.PySegment): flame api object - - Returns: - flame.PyMarker: flame api object - """ - # get duration of segment - duration = segment.record_duration.relative_frame - # calculate start frame of the new marker - start_frame = int(segment.record_in.relative_frame) + int(duration / 2) - # create marker - marker = segment.create_marker(start_frame) - # set marker name - marker.name = MARKER_NAME - # set duration - marker.duration = MARKER_DURATION - # set colour - marker.colour = COLOR_MAP[MARKER_COLOR] # Red - - return marker - - -def get_sequence_segments(sequence, selected=False): - segments = [] - # loop versions in sequence - for ver in sequence.versions: - # loop track in versions - for track in ver.tracks: - # ignore all empty tracks and hidden too - if len(track.segments) == 0 and track.hidden: - continue - # loop all segment in remaining tracks - for segment in track.segments: - if segment.name.get_value() == "": - continue - if segment.hidden.get_value() is True: - continue - if ( - selected is True - and segment.selected.get_value() is not True - ): - continue - # add it to original selection - segments.append(segment) - return segments - - -@contextlib.contextmanager -def maintained_segment_selection(sequence): - """Maintain selection during context - - Attributes: - sequence (flame.PySequence): python api object - - Yield: - list of flame.PySegment - - Example: - >>> with maintained_segment_selection(sequence) as selected_segments: - ... for segment in selected_segments: - ... segment.selected = False - >>> print(segment.selected) - True - """ - selected_segments = get_sequence_segments(sequence, True) - try: - # do the operation on selected segments - yield selected_segments - finally: - # reset all selected clips - reset_segment_selection(sequence) - # select only original selection of segments - for segment in selected_segments: - segment.selected = True - - -def reset_segment_selection(sequence): - """Deselect all selected nodes - """ - for ver in sequence.versions: - for track in ver.tracks: - if len(track.segments) == 0 and track.hidden: - continue - for segment in track.segments: - segment.selected = False - - -def _get_shot_tokens_values(clip, tokens): - old_value = None - output = {} - - if not clip.shot_name: - return output - - old_value = clip.shot_name.get_value() - - for token in tokens: - clip.shot_name.set_value(token) - _key = str(re.sub("[<>]", "", token)).replace(" ", "_") - - try: - output[_key] = int(clip.shot_name.get_value()) - except ValueError: - output[_key] = clip.shot_name.get_value() - - clip.shot_name.set_value(old_value) - - return output - - -def get_segment_attributes(segment): - if segment.name.get_value() == "": - return None - - # Add timeline segment to tree - clip_data = { - "shot_name": segment.shot_name.get_value(), - "segment_name": segment.name.get_value(), - "segment_comment": segment.comment.get_value(), - "tape_name": segment.tape_name, - "source_name": segment.source_name, - "fpath": segment.file_path, - "PySegment": segment - } - - # head and tail with forward compatibility - if segment.head: - # `infinite` can be also returned - if isinstance(segment.head, str): - clip_data["segment_head"] = 0 - else: - clip_data["segment_head"] = int(segment.head) - if segment.tail: - # `infinite` can be also returned - if isinstance(segment.tail, str): - clip_data["segment_tail"] = 0 - else: - clip_data["segment_tail"] = int(segment.tail) - - # add all available shot tokens - shot_tokens = _get_shot_tokens_values(segment, [ - "", "", "", "", "", - "", "" - ]) - clip_data.update(shot_tokens) - - # populate shot source metadata - segment_attrs = [ - "record_duration", "record_in", "record_out", - "source_duration", "source_in", "source_out" - ] - segment_attrs_data = {} - for attr_name in segment_attrs: - if not hasattr(segment, attr_name): - continue - attr = getattr(segment, attr_name) - segment_attrs_data[attr_name] = str(attr).replace("+", ":") - - if attr_name in ["record_in", "record_out"]: - clip_data[attr_name] = attr.relative_frame - else: - clip_data[attr_name] = attr.frame - - 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_reformatted_filename(filename, padded=True): - """ - Return fixed python expression path - - Args: - filename (str): file name - - Returns: - type: string with reformatted path - - Example: - get_reformatted_filename("plate.1001.exr") > plate.%04d.exr - - """ - found = FRAME_PATTERN.search(filename) - - if not found: - log.info("File name is not sequence: {}".format(filename)) - return filename - - padding = get_padding_from_filename(filename) - - 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_filename(filename): - """ - Return padding number from Flame path style - - Args: - filename (str): file name - - Returns: - int: padding number - - Example: - get_padding_from_filename("plate.0001.exr") > 4 - - """ - found = get_frame_from_filename(filename) - - return len(found) if found else None - - -def get_frame_from_filename(filename): - """ - Return sequence number from Flame path style - - Args: - filename (str): file name - - Returns: - int: sequence frame number - - Example: - def get_frame_from_filename(path): - ("plate.0001.exr") > 0001 - - """ - - found = re.findall(FRAME_PATTERN, filename) - - return found.pop() if found else None - - -@contextlib.contextmanager -def maintained_object_duplication(item): - """Maintain input item duplication - - Attributes: - item (any flame.PyObject): python api object - - Yield: - duplicate input PyObject type - """ - import flame - # Duplicate the clip to avoid modifying the original clip - duplicate = flame.duplicate(item) - - try: - # do the operation on selected segments - yield duplicate - finally: - # delete the item at the end - flame.delete(duplicate) - - -@contextlib.contextmanager -def maintained_temp_file_path(suffix=None): - _suffix = suffix or "" - - try: - # Store dumped json to temporary file - temporary_file = tempfile.mktemp( - suffix=_suffix, prefix="flame_maintained_") - yield temporary_file.replace("\\", "/") - - except IOError as _error: - raise IOError( - "Not able to create temp json file: {}".format(_error)) - - finally: - # Remove the temporary json - os.remove(temporary_file) - - -def get_clip_segment(flame_clip): - name = flame_clip.name.get_value() - version = flame_clip.versions[0] - track = version.tracks[0] - segments = track.segments - - if len(segments) < 1: - raise ValueError("Clip `{}` has no segments!".format(name)) - - if len(segments) > 1: - raise ValueError("Clip `{}` has too many segments!".format(name)) - - return segments[0] - - -def get_batch_group_from_desktop(name): - project = get_current_project() - project_desktop = project.current_workspace.desktop - - for bgroup in project_desktop.batch_groups: - if bgroup.name.get_value() in name: - return bgroup - - -class MediaInfoFile(object): - """Class to get media info file clip data - - Raises: - IOError: MEDIA_SCRIPT_PATH path doesn't exists - TypeError: Not able to generate clip xml data file - ET.ParseError: Missing clip in xml clip data - IOError: Not able to save xml clip data to file - - Attributes: - str: `MEDIA_SCRIPT_PATH` path to flame binary - logging.Logger: `log` logger - - TODO: add method for getting metadata to dict - """ - MEDIA_SCRIPT_PATH = "/opt/Autodesk/mio/current/dl_get_media_info" - - log = log - - _clip_data = None - _start_frame = None - _fps = None - _drop_mode = None - _file_pattern = None - - def __init__(self, path, logger=None): - - # replace log if any - if logger: - self.log = logger - - # test if `dl_get_media_info` path exists - self._validate_media_script_path() - - # derivate other feed variables - feed_basename = os.path.basename(path) - feed_dir = os.path.dirname(path) - feed_ext = os.path.splitext(feed_basename)[1][1:].lower() - - with maintained_temp_file_path(".clip") as tmp_path: - self.log.info("Temp File: {}".format(tmp_path)) - self._generate_media_info_file(tmp_path, feed_ext, feed_dir) - - # get collection containing feed_basename from path - self.file_pattern = self._get_collection( - feed_basename, feed_dir, feed_ext) - - if ( - not self.file_pattern - and os.path.exists(os.path.join(feed_dir, feed_basename)) - ): - self.file_pattern = feed_basename - - # get clip data and make them single if there is multiple - # clips data - xml_data = self._make_single_clip_media_info( - tmp_path, feed_basename, self.file_pattern) - self.log.debug("xml_data: {}".format(xml_data)) - self.log.debug("type: {}".format(type(xml_data))) - - # get all time related data and assign them - self._get_time_info_from_origin(xml_data) - self.log.debug("start_frame: {}".format(self.start_frame)) - self.log.debug("fps: {}".format(self.fps)) - self.log.debug("drop frame: {}".format(self.drop_mode)) - self.clip_data = xml_data - - def _get_collection(self, feed_basename, feed_dir, feed_ext): - """ Get collection string - - Args: - feed_basename (str): file base name - feed_dir (str): file's directory - feed_ext (str): file extension - - Raises: - AttributeError: feed_ext is not matching feed_basename - - Returns: - str: collection basename with range of sequence - """ - partialname = self._separate_file_head(feed_basename, feed_ext) - self.log.debug("__ partialname: {}".format(partialname)) - - # make sure partial input basename is having correct extensoon - if not partialname: - raise AttributeError( - "Wrong input attributes. Basename - {}, Ext - {}".format( - feed_basename, feed_ext - ) - ) - - # get all related files - files = [ - f for f in os.listdir(feed_dir) - if partialname == self._separate_file_head(f, feed_ext) - ] - - # ignore reminders as we dont need them - collections = clique.assemble(files)[0] - - # in case no collection found return None - # it is probably just single file - if not collections: - return - - # we expect only one collection - collection = collections[0] - - self.log.debug("__ collection: {}".format(collection)) - - if collection.is_contiguous(): - return self._format_collection(collection) - - # add `[` in front to make sure it want capture - # shot name with the same number - number_from_path = self._separate_number(feed_basename, feed_ext) - search_number_pattern = "[" + number_from_path - # convert to multiple collections - _continues_colls = collection.separate() - for _coll in _continues_colls: - coll_to_text = self._format_collection( - _coll, len(number_from_path)) - self.log.debug("__ coll_to_text: {}".format(coll_to_text)) - if search_number_pattern in coll_to_text: - return coll_to_text - - @staticmethod - def _format_collection(collection, padding=None): - padding = padding or collection.padding - # if no holes then return collection - head = collection.format("{head}") - tail = collection.format("{tail}") - range_template = "[{{:0{0}d}}-{{:0{0}d}}]".format( - padding) - ranges = range_template.format( - min(collection.indexes), - max(collection.indexes) - ) - # if no holes then return collection - return "{}{}{}".format(head, ranges, tail) - - def _separate_file_head(self, basename, extension): - """ Get only head with out sequence and extension - - Args: - basename (str): file base name - extension (str): file extension - - Returns: - str: file head - """ - # in case sequence file - found = re.findall( - r"(.*)[._][\d]*(?=.{})".format(extension), - basename, - ) - if found: - return found.pop() - - # in case single file - name, ext = os.path.splitext(basename) - - if extension == ext[1:]: - return name - - def _separate_number(self, basename, extension): - """ Get only sequence number as string - - Args: - basename (str): file base name - extension (str): file extension - - Returns: - str: number with padding - """ - # in case sequence file - found = re.findall( - r"[._]([\d]*)(?=.{})".format(extension), - basename, - ) - if found: - return found.pop() - - @property - def clip_data(self): - """Clip's xml clip data - - Returns: - xml.etree.ElementTree: xml data - """ - return self._clip_data - - @clip_data.setter - def clip_data(self, data): - self._clip_data = data - - @property - def start_frame(self): - """ Clip's starting frame found in timecode - - Returns: - int: number of frames - """ - return self._start_frame - - @start_frame.setter - def start_frame(self, number): - self._start_frame = int(number) - - @property - def fps(self): - """ Clip's frame rate - - Returns: - float: frame rate - """ - return self._fps - - @fps.setter - def fps(self, fl_number): - self._fps = float(fl_number) - - @property - def drop_mode(self): - """ Clip's drop frame mode - - Returns: - str: drop frame flag - """ - return self._drop_mode - - @drop_mode.setter - def drop_mode(self, text): - self._drop_mode = str(text) - - @property - def file_pattern(self): - """Clips file pattern. - - Returns: - str: file pattern. ex. file.[1-2].exr - """ - return self._file_pattern - - @file_pattern.setter - def file_pattern(self, fpattern): - self._file_pattern = fpattern - - def _validate_media_script_path(self): - if not os.path.isfile(self.MEDIA_SCRIPT_PATH): - raise IOError("Media Script does not exist: `{}`".format( - self.MEDIA_SCRIPT_PATH)) - - def _generate_media_info_file(self, fpath, feed_ext, feed_dir): - """ Generate media info xml .clip file - - Args: - fpath (str): .clip file path - feed_ext (str): file extension to be filtered - feed_dir (str): look up directory - - Raises: - TypeError: Type error if it fails - """ - # Create cmd arguments for gettig xml file info file - cmd_args = [ - self.MEDIA_SCRIPT_PATH, - "-e", feed_ext, - "-o", fpath, - feed_dir - ] - - try: - # execute creation of clip xml template data - run_subprocess(cmd_args) - except TypeError as error: - raise TypeError( - "Error creating `{}` due: {}".format(fpath, error)) - - def _make_single_clip_media_info(self, fpath, feed_basename, path_pattern): - """ Separate only relative clip object form .clip file - - Args: - fpath (str): clip file path - feed_basename (str): search basename - path_pattern (str): search file pattern (file.[1-2].exr) - - Raises: - ET.ParseError: if nothing found - - Returns: - ET.Element: xml element data of matching clip - """ - with open(fpath) as f: - lines = f.readlines() - _added_root = itertools.chain( - "", deepcopy(lines)[1:], "") - new_root = ET.fromstringlist(_added_root) - - # find the clip which is matching to my input name - xml_clips = new_root.findall("clip") - matching_clip = None - for xml_clip in xml_clips: - clip_name = xml_clip.find("name").text - self.log.debug("__ clip_name: `{}`".format(clip_name)) - if clip_name not in feed_basename: - continue - - # test path pattern - for out_track in xml_clip.iter("track"): - for out_feed in out_track.iter("feed"): - for span in out_feed.iter("span"): - # start frame - span_path = span.find("path") - self.log.debug( - "__ span_path.text: {}, path_pattern: {}".format( - span_path.text, path_pattern - ) - ) - if path_pattern in span_path.text: - matching_clip = xml_clip - - if matching_clip is None: - # return warning there is missing clip - raise ET.ParseError( - "Missing clip in `{}`. Available clips {}".format( - feed_basename, [ - xml_clip.find("name").text - for xml_clip in xml_clips - ] - )) - - return matching_clip - - def _get_time_info_from_origin(self, xml_data): - """Set time info to class attributes - - Args: - xml_data (ET.Element): clip data - """ - try: - for out_track in xml_data.iter("track"): - for out_feed in out_track.iter("feed"): - # start frame - out_feed_nb_ticks_obj = out_feed.find( - "startTimecode/nbTicks") - self.start_frame = out_feed_nb_ticks_obj.text - - # fps - out_feed_fps_obj = out_feed.find( - "startTimecode/rate") - self.fps = out_feed_fps_obj.text - - # drop frame mode - out_feed_drop_mode_obj = out_feed.find( - "startTimecode/dropMode") - self.drop_mode = out_feed_drop_mode_obj.text - break - except Exception as msg: - self.log.warning(msg) - - @staticmethod - def write_clip_data_to_file(fpath, xml_element_data): - """ Write xml element of clip data to file - - Args: - fpath (string): file path - xml_element_data (xml.etree.ElementTree.Element): xml data - - Raises: - IOError: If data could not be written to file - """ - try: - # save it as new file - tree = cET.ElementTree(xml_element_data) - tree.write( - fpath, xml_declaration=True, - method="xml", encoding="UTF-8" - ) - except IOError as error: - raise IOError( - "Not able to write data to file: {}".format(error)) - - -class TimeEffectMetadata(object): - log = log - _data = {} - _retime_modes = { - 0: "speed", - 1: "timewarp", - 2: "duration" - } - - def __init__(self, segment, logger=None): - if logger: - self.log = logger - - self._data = self._get_metadata(segment) - - @property - def data(self): - """ Returns timewarp effect data - - Returns: - dict: retime data - """ - return self._data - - def _get_metadata(self, segment): - effects = segment.effects or [] - for effect in effects: - if effect.type == "Timewarp": - with maintained_temp_file_path(".timewarp_node") as tmp_path: - self.log.info("Temp File: {}".format(tmp_path)) - effect.save_setup(tmp_path) - return self._get_attributes_from_xml(tmp_path) - - return {} - - def _get_attributes_from_xml(self, tmp_path): - with open(tmp_path, "r") as tw_setup_file: - tw_setup_string = tw_setup_file.read() - tw_setup_file.close() - - tw_setup_xml = ET.fromstring(tw_setup_string) - tw_setup = self._dictify(tw_setup_xml) - # pprint(tw_setup) - try: - tw_setup_state = tw_setup["Setup"]["State"][0] - mode = int( - tw_setup_state["TW_RetimerMode"][0]["_text"] - ) - r_data = { - "type": self._retime_modes[mode], - "effectStart": int( - tw_setup["Setup"]["Base"][0]["Range"][0]["Start"]), - "effectEnd": int( - tw_setup["Setup"]["Base"][0]["Range"][0]["End"]) - } - - if mode == 0: # speed - r_data[self._retime_modes[mode]] = float( - tw_setup_state["TW_Speed"] - [0]["Channel"][0]["Value"][0]["_text"] - ) / 100 - elif mode == 1: # timewarp - print("timing") - r_data[self._retime_modes[mode]] = self._get_anim_keys( - tw_setup_state["TW_Timing"] - ) - elif mode == 2: # duration - r_data[self._retime_modes[mode]] = { - "start": { - "source": int( - tw_setup_state["TW_DurationTiming"][0]["Channel"] - [0]["KFrames"][0]["Key"][0]["Value"][0]["_text"] - ), - "timeline": int( - tw_setup_state["TW_DurationTiming"][0]["Channel"] - [0]["KFrames"][0]["Key"][0]["Frame"][0]["_text"] - ) - }, - "end": { - "source": int( - tw_setup_state["TW_DurationTiming"][0]["Channel"] - [0]["KFrames"][0]["Key"][1]["Value"][0]["_text"] - ), - "timeline": int( - tw_setup_state["TW_DurationTiming"][0]["Channel"] - [0]["KFrames"][0]["Key"][1]["Frame"][0]["_text"] - ) - } - } - except Exception: - lines = traceback.format_exception(*sys.exc_info()) - self.log.error("\n".join(lines)) - return - - return r_data - - def _get_anim_keys(self, setup_cat, index=None): - return_data = { - "extrapolation": ( - setup_cat[0]["Channel"][0]["Extrap"][0]["_text"] - ), - "animKeys": [] - } - for key in setup_cat[0]["Channel"][0]["KFrames"][0]["Key"]: - if index and int(key["Index"]) != index: - continue - key_data = { - "source": float(key["Value"][0]["_text"]), - "timeline": float(key["Frame"][0]["_text"]), - "index": int(key["Index"]), - "curveMode": key["CurveMode"][0]["_text"], - "curveOrder": key["CurveOrder"][0]["_text"] - } - if key.get("TangentMode"): - key_data["tangentMode"] = key["TangentMode"][0]["_text"] - - return_data["animKeys"].append(key_data) - - return return_data - - def _dictify(self, xml_, root=True): - """ Convert xml object to dictionary - - Args: - xml_ (xml.etree.ElementTree.Element): xml data - root (bool, optional): is root available. Defaults to True. - - Returns: - dict: dictionarized xml - """ - - if root: - return {xml_.tag: self._dictify(xml_, False)} - - d = copy(xml_.attrib) - if xml_.text: - d["_text"] = xml_.text - - for x in xml_.findall("./*"): - if x.tag not in d: - d[x.tag] = [] - d[x.tag].append(self._dictify(x, False)) - return d diff --git a/server_addon/flame/client/ayon_flame/api/menu.py b/server_addon/flame/client/ayon_flame/api/menu.py deleted file mode 100644 index 83d75d18d3..0000000000 --- a/server_addon/flame/client/ayon_flame/api/menu.py +++ /dev/null @@ -1,256 +0,0 @@ -from copy import deepcopy -from pprint import pformat - -from qtpy import QtWidgets - -from ayon_core.pipeline import get_current_project_name -from ayon_core.tools.utils.host_tools import HostToolsHelper - -menu_group_name = 'OpenPype' - -default_flame_export_presets = { - 'Publish': { - 'PresetVisibility': 2, - 'PresetType': 0, - 'PresetFile': 'OpenEXR/OpenEXR (16-bit fp PIZ).xml' - }, - 'Preview': { - 'PresetVisibility': 3, - 'PresetType': 2, - 'PresetFile': 'Generate Preview.xml' - }, - 'Thumbnail': { - 'PresetVisibility': 3, - 'PresetType': 0, - 'PresetFile': 'Generate Thumbnail.xml' - } -} - - -def callback_selection(selection, function): - import ayon_flame.api as opfapi - opfapi.CTX.selection = selection - print("Hook Selection: \n\t{}".format( - pformat({ - index: (type(item), item.name) - for index, item in enumerate(opfapi.CTX.selection)}) - )) - function() - - -class _FlameMenuApp(object): - def __init__(self, framework): - self.name = self.__class__.__name__ - self.framework = framework - self.log = framework.log - self.menu_group_name = menu_group_name - self.dynamic_menu_data = {} - - # flame module is only available when a - # flame project is loaded and initialized - self.flame = None - try: - import flame - self.flame = flame - except ImportError: - self.flame = None - - self.flame_project_name = flame.project.current_project.name - self.prefs = self.framework.prefs_dict(self.framework.prefs, self.name) - self.prefs_user = self.framework.prefs_dict( - self.framework.prefs_user, self.name) - self.prefs_global = self.framework.prefs_dict( - self.framework.prefs_global, self.name) - - self.mbox = QtWidgets.QMessageBox() - project_name = get_current_project_name() - self.menu = { - "actions": [{ - 'name': project_name or "project", - 'isEnabled': False - }], - "name": self.menu_group_name - } - self.tools_helper = HostToolsHelper() - - def __getattr__(self, name): - def method(*args, **kwargs): - print('calling %s' % name) - return method - - def rescan(self, *args, **kwargs): - if not self.flame: - try: - import flame - self.flame = flame - except ImportError: - self.flame = None - - if self.flame: - self.flame.execute_shortcut('Rescan Python Hooks') - self.log.info('Rescan Python Hooks') - - -class FlameMenuProjectConnect(_FlameMenuApp): - - # flameMenuProjectconnect app takes care of the preferences dialog as well - - def __init__(self, framework): - _FlameMenuApp.__init__(self, framework) - - def __getattr__(self, name): - def method(*args, **kwargs): - project = self.dynamic_menu_data.get(name) - if project: - self.link_project(project) - return method - - def build_menu(self): - if not self.flame: - return [] - - menu = deepcopy(self.menu) - - menu['actions'].append({ - "name": "Workfiles...", - "execute": lambda x: self.tools_helper.show_workfiles() - }) - menu['actions'].append({ - "name": "Load...", - "execute": lambda x: self.tools_helper.show_loader() - }) - menu['actions'].append({ - "name": "Manage...", - "execute": lambda x: self.tools_helper.show_scene_inventory() - }) - menu['actions'].append({ - "name": "Library...", - "execute": lambda x: self.tools_helper.show_library_loader() - }) - return menu - - def refresh(self, *args, **kwargs): - self.rescan() - - def rescan(self, *args, **kwargs): - if not self.flame: - try: - import flame - self.flame = flame - except ImportError: - self.flame = None - - if self.flame: - self.flame.execute_shortcut('Rescan Python Hooks') - self.log.info('Rescan Python Hooks') - - -class FlameMenuTimeline(_FlameMenuApp): - - # flameMenuProjectconnect app takes care of the preferences dialog as well - - def __init__(self, framework): - _FlameMenuApp.__init__(self, framework) - - def __getattr__(self, name): - def method(*args, **kwargs): - project = self.dynamic_menu_data.get(name) - if project: - self.link_project(project) - return method - - def build_menu(self): - if not self.flame: - return [] - - menu = deepcopy(self.menu) - - menu['actions'].append({ - "name": "Create...", - "execute": lambda x: callback_selection( - x, self.tools_helper.show_creator) - }) - menu['actions'].append({ - "name": "Publish...", - "execute": lambda x: callback_selection( - x, self.tools_helper.show_publish) - }) - menu['actions'].append({ - "name": "Load...", - "execute": lambda x: self.tools_helper.show_loader() - }) - menu['actions'].append({ - "name": "Manage...", - "execute": lambda x: self.tools_helper.show_scene_inventory() - }) - menu['actions'].append({ - "name": "Library...", - "execute": lambda x: self.tools_helper.show_library_loader() - }) - return menu - - def refresh(self, *args, **kwargs): - self.rescan() - - def rescan(self, *args, **kwargs): - if not self.flame: - try: - import flame - self.flame = flame - except ImportError: - self.flame = None - - if self.flame: - self.flame.execute_shortcut('Rescan Python Hooks') - self.log.info('Rescan Python Hooks') - - -class FlameMenuUniversal(_FlameMenuApp): - - # flameMenuProjectconnect app takes care of the preferences dialog as well - - def __init__(self, framework): - _FlameMenuApp.__init__(self, framework) - - def __getattr__(self, name): - def method(*args, **kwargs): - project = self.dynamic_menu_data.get(name) - if project: - self.link_project(project) - return method - - def build_menu(self): - if not self.flame: - return [] - - menu = deepcopy(self.menu) - - menu['actions'].append({ - "name": "Load...", - "execute": lambda x: callback_selection( - x, self.tools_helper.show_loader) - }) - menu['actions'].append({ - "name": "Manage...", - "execute": lambda x: self.tools_helper.show_scene_inventory() - }) - menu['actions'].append({ - "name": "Library...", - "execute": lambda x: self.tools_helper.show_library_loader() - }) - return menu - - def refresh(self, *args, **kwargs): - self.rescan() - - def rescan(self, *args, **kwargs): - if not self.flame: - try: - import flame - self.flame = flame - except ImportError: - self.flame = None - - if self.flame: - self.flame.execute_shortcut('Rescan Python Hooks') - self.log.info('Rescan Python Hooks') diff --git a/server_addon/flame/client/ayon_flame/api/pipeline.py b/server_addon/flame/client/ayon_flame/api/pipeline.py deleted file mode 100644 index 121b925920..0000000000 --- a/server_addon/flame/client/ayon_flame/api/pipeline.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -Basic avalon integration -""" -import os -import contextlib -from pyblish import api as pyblish - -from ayon_core.lib import Logger -from ayon_core.pipeline import ( - register_loader_plugin_path, - register_creator_plugin_path, - deregister_loader_plugin_path, - deregister_creator_plugin_path, - AVALON_CONTAINER_ID, -) -from ayon_flame import FLAME_ADDON_ROOT -from .lib import ( - set_segment_data_marker, - set_publish_attribute, - maintained_segment_selection, - get_current_sequence, - reset_segment_selection -) - -PLUGINS_DIR = os.path.join(FLAME_ADDON_ROOT, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "create") - -AVALON_CONTAINERS = "AVALON_CONTAINERS" - -log = Logger.get_logger(__name__) - - -def install(): - pyblish.register_host("flame") - pyblish.register_plugin_path(PUBLISH_PATH) - register_loader_plugin_path(LOAD_PATH) - register_creator_plugin_path(CREATE_PATH) - log.info("AYON Flame plug-ins registered ...") - - # register callback for switching publishable - pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) - - log.info("AYON Flame host installed ...") - - -def uninstall(): - pyblish.deregister_host("flame") - - log.info("Deregistering Flame plug-ins..") - pyblish.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugin_path(LOAD_PATH) - deregister_creator_plugin_path(CREATE_PATH) - - # register callback for switching publishable - pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) - - log.info("AYON Flame host uninstalled ...") - - -def containerise(flame_clip_segment, - name, - namespace, - context, - loader=None, - data=None): - - data_imprint = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, - "name": str(name), - "namespace": str(namespace), - "loader": str(loader), - "representation": context["representation"]["id"], - } - - if data: - for k, v in data.items(): - data_imprint[k] = v - - log.debug("_ data_imprint: {}".format(data_imprint)) - - set_segment_data_marker(flame_clip_segment, data_imprint) - - return True - - -def ls(): - """List available containers. - """ - return [] - - -def parse_container(tl_segment, validate=True): - """Return container data from timeline_item's openpype tag. - """ - # TODO: parse_container - pass - - -def update_container(tl_segment, data=None): - """Update container data to input timeline_item's openpype tag. - """ - # TODO: update_container - pass - - -def on_pyblish_instance_toggled(instance, old_value, new_value): - """Toggle node passthrough states on instance toggles.""" - - log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( - instance, old_value, new_value)) - - # # Whether instances should be passthrough based on new value - # timeline_item = instance.data["item"] - # set_publish_attribute(timeline_item, new_value) - - -def remove_instance(instance): - """Remove instance marker from track item.""" - # TODO: remove_instance - pass - - -def list_instances(): - """List all created instances from current workfile.""" - # TODO: list_instances - pass - - -def imprint(segment, data=None): - """ - Adding openpype data to Flame timeline segment. - - Also including publish attribute into tag. - - Arguments: - segment (flame.PySegment)): flame api object - data (dict): Any data which needst to be imprinted - - Examples: - data = { - 'asset': 'sq020sh0280', - 'productType': 'render', - 'productName': 'productMain' - } - """ - data = data or {} - - set_segment_data_marker(segment, data) - - # add publish attribute - set_publish_attribute(segment, True) - - -@contextlib.contextmanager -def maintained_selection(): - import flame - from .lib import CTX - - # check if segment is selected - if isinstance(CTX.selection[0], flame.PySegment): - sequence = get_current_sequence(CTX.selection) - - try: - with maintained_segment_selection(sequence) as selected: - yield - finally: - # reset all selected clips - reset_segment_selection(sequence) - # select only original selection of segments - for segment in selected: - segment.selected = True diff --git a/server_addon/flame/client/ayon_flame/api/plugin.py b/server_addon/flame/client/ayon_flame/api/plugin.py deleted file mode 100644 index e656f33052..0000000000 --- a/server_addon/flame/client/ayon_flame/api/plugin.py +++ /dev/null @@ -1,1089 +0,0 @@ -import os -import re -import shutil -from copy import deepcopy -from xml.etree import ElementTree as ET - -import qargparse -from qtpy import QtCore, QtWidgets - -from ayon_core import style -from ayon_core.lib import Logger, StringTemplate -from ayon_core.pipeline import LegacyCreator, LoaderPlugin -from ayon_core.pipeline.colorspace import get_remapped_colorspace_to_native -from ayon_core.settings import get_current_project_settings - -from . import constants -from . import lib as flib -from . import pipeline as fpipeline - -log = Logger.get_logger(__name__) - - -class CreatorWidget(QtWidgets.QDialog): - - # output items - items = dict() - _results_back = None - - def __init__(self, name, info, ui_inputs, parent=None): - super(CreatorWidget, self).__init__(parent) - - self.setObjectName(name) - - self.setWindowFlags( - QtCore.Qt.Window - | QtCore.Qt.CustomizeWindowHint - | QtCore.Qt.WindowTitleHint - | QtCore.Qt.WindowCloseButtonHint - | QtCore.Qt.WindowStaysOnTopHint - ) - self.setWindowTitle(name or "AYON Creator Input") - self.resize(500, 700) - - # Where inputs and labels are set - self.content_widget = [QtWidgets.QWidget(self)] - top_layout = QtWidgets.QFormLayout(self.content_widget[0]) - top_layout.setObjectName("ContentLayout") - top_layout.addWidget(Spacer(5, self)) - - # first add widget tag line - top_layout.addWidget(QtWidgets.QLabel(info)) - - # main dynamic layout - self.scroll_area = QtWidgets.QScrollArea(self, widgetResizable=True) - self.scroll_area.setVerticalScrollBarPolicy( - QtCore.Qt.ScrollBarAsNeeded) - self.scroll_area.setVerticalScrollBarPolicy( - QtCore.Qt.ScrollBarAlwaysOn) - self.scroll_area.setHorizontalScrollBarPolicy( - QtCore.Qt.ScrollBarAlwaysOff) - self.scroll_area.setWidgetResizable(True) - - self.content_widget.append(self.scroll_area) - - scroll_widget = QtWidgets.QWidget(self) - in_scroll_area = QtWidgets.QVBoxLayout(scroll_widget) - self.content_layout = [in_scroll_area] - - # add preset data into input widget layout - self.items = self.populate_widgets(ui_inputs) - self.scroll_area.setWidget(scroll_widget) - - # Confirmation buttons - btns_widget = QtWidgets.QWidget(self) - btns_layout = QtWidgets.QHBoxLayout(btns_widget) - - cancel_btn = QtWidgets.QPushButton("Cancel") - btns_layout.addWidget(cancel_btn) - - ok_btn = QtWidgets.QPushButton("Ok") - btns_layout.addWidget(ok_btn) - - # Main layout of the dialog - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) - main_layout.setSpacing(0) - - # adding content widget - for w in self.content_widget: - main_layout.addWidget(w) - - main_layout.addWidget(btns_widget) - - ok_btn.clicked.connect(self._on_ok_clicked) - cancel_btn.clicked.connect(self._on_cancel_clicked) - - self.setStyleSheet(style.load_stylesheet()) - - @classmethod - def set_results_back(cls, value): - cls._results_back = value - - @classmethod - def get_results_back(cls): - return cls._results_back - - def _on_ok_clicked(self): - log.debug("ok is clicked: {}".format(self.items)) - results_back = self._values(self.items) - self.set_results_back(results_back) - self.close() - - def _on_cancel_clicked(self): - self.set_results_back(None) - self.close() - - def showEvent(self, event): - self.set_results_back(None) - super(CreatorWidget, self).showEvent(event) - - def _values(self, data, new_data=None): - new_data = new_data or dict() - for k, v in data.items(): - new_data[k] = { - "target": None, - "value": None - } - if v["type"] == "dict": - new_data[k]["target"] = v["target"] - new_data[k]["value"] = self._values(v["value"]) - if v["type"] == "section": - new_data.pop(k) - new_data = self._values(v["value"], new_data) - elif getattr(v["value"], "currentText", None): - new_data[k]["target"] = v["target"] - new_data[k]["value"] = v["value"].currentText() - elif getattr(v["value"], "isChecked", None): - new_data[k]["target"] = v["target"] - new_data[k]["value"] = v["value"].isChecked() - elif getattr(v["value"], "value", None): - new_data[k]["target"] = v["target"] - new_data[k]["value"] = v["value"].value() - elif getattr(v["value"], "text", None): - new_data[k]["target"] = v["target"] - new_data[k]["value"] = v["value"].text() - - return new_data - - def camel_case_split(self, text): - matches = re.finditer( - '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) - return " ".join([str(m.group(0)).capitalize() for m in matches]) - - def create_row(self, layout, type_name, text, **kwargs): - # get type attribute from qwidgets - attr = getattr(QtWidgets, type_name) - - # convert label text to normal capitalized text with spaces - label_text = self.camel_case_split(text) - - # assign the new text to label widget - label = QtWidgets.QLabel(label_text) - label.setObjectName("LineLabel") - - # create attribute name text strip of spaces - attr_name = text.replace(" ", "") - - # create attribute and assign default values - setattr( - self, - attr_name, - attr(parent=self)) - - # assign the created attribute to variable - item = getattr(self, attr_name) - for func, val in kwargs.items(): - if getattr(item, func): - func_attr = getattr(item, func) - func_attr(val) - - # add to layout - layout.addRow(label, item) - - return item - - def populate_widgets(self, data, content_layout=None): - """ - Populate widget from input dict. - - Each plugin has its own set of widget rows defined in dictionary - each row values should have following keys: `type`, `target`, - `label`, `order`, `value` and optionally also `toolTip`. - - Args: - data (dict): widget rows or organized groups defined - by types `dict` or `section` - content_layout (QtWidgets.QFormLayout)[optional]: used when nesting - - Returns: - dict: redefined data dict updated with created widgets - - """ - - content_layout = content_layout or self.content_layout[-1] - # fix order of process by defined order value - ordered_keys = list(data.keys()) - for k, v in data.items(): - try: - # try removing a key from index which should - # be filled with new - ordered_keys.pop(v["order"]) - except IndexError: - pass - # add key into correct order - ordered_keys.insert(v["order"], k) - - # process ordered - for k in ordered_keys: - v = data[k] - tool_tip = v.get("toolTip", "") - if v["type"] == "dict": - self.content_layout.append(QtWidgets.QWidget(self)) - content_layout.addWidget(self.content_layout[-1]) - self.content_layout[-1].setObjectName("sectionHeadline") - - headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) - headline.addWidget(Spacer(20, self)) - headline.addWidget(QtWidgets.QLabel(v["label"])) - - # adding nested layout with label - self.content_layout.append(QtWidgets.QWidget(self)) - self.content_layout[-1].setObjectName("sectionContent") - - nested_content_layout = QtWidgets.QFormLayout( - self.content_layout[-1]) - nested_content_layout.setObjectName("NestedContentLayout") - content_layout.addWidget(self.content_layout[-1]) - - # add nested key as label - data[k]["value"] = self.populate_widgets( - v["value"], nested_content_layout) - - if v["type"] == "section": - self.content_layout.append(QtWidgets.QWidget(self)) - content_layout.addWidget(self.content_layout[-1]) - self.content_layout[-1].setObjectName("sectionHeadline") - - headline = QtWidgets.QVBoxLayout(self.content_layout[-1]) - headline.addWidget(Spacer(20, self)) - headline.addWidget(QtWidgets.QLabel(v["label"])) - - # adding nested layout with label - self.content_layout.append(QtWidgets.QWidget(self)) - self.content_layout[-1].setObjectName("sectionContent") - - nested_content_layout = QtWidgets.QFormLayout( - self.content_layout[-1]) - nested_content_layout.setObjectName("NestedContentLayout") - content_layout.addWidget(self.content_layout[-1]) - - # add nested key as label - data[k]["value"] = self.populate_widgets( - v["value"], nested_content_layout) - - elif v["type"] == "QLineEdit": - data[k]["value"] = self.create_row( - content_layout, "QLineEdit", v["label"], - setText=v["value"], setToolTip=tool_tip) - elif v["type"] == "QComboBox": - data[k]["value"] = self.create_row( - content_layout, "QComboBox", v["label"], - addItems=v["value"], setToolTip=tool_tip) - elif v["type"] == "QCheckBox": - data[k]["value"] = self.create_row( - content_layout, "QCheckBox", v["label"], - setChecked=v["value"], setToolTip=tool_tip) - elif v["type"] == "QSpinBox": - data[k]["value"] = self.create_row( - content_layout, "QSpinBox", v["label"], - setValue=v["value"], setMinimum=0, - setMaximum=100000, setToolTip=tool_tip) - return data - - -class Spacer(QtWidgets.QWidget): - def __init__(self, height, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) - - self.setFixedHeight(height) - - real_spacer = QtWidgets.QWidget(self) - real_spacer.setObjectName("Spacer") - real_spacer.setFixedHeight(height) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(real_spacer) - - self.setLayout(layout) - - -class Creator(LegacyCreator): - """Creator class wrapper - """ - clip_color = constants.COLOR_MAP["purple"] - rename_index = None - - def __init__(self, *args, **kwargs): - super(Creator, self).__init__(*args, **kwargs) - self.presets = get_current_project_settings()[ - "flame"]["create"].get(self.__class__.__name__, {}) - - # adding basic current context flame objects - self.project = flib.get_current_project() - self.sequence = flib.get_current_sequence(flib.CTX.selection) - - if (self.options or {}).get("useSelection"): - self.selected = flib.get_sequence_segments(self.sequence, True) - else: - self.selected = flib.get_sequence_segments(self.sequence) - - def create_widget(self, *args, **kwargs): - widget = CreatorWidget(*args, **kwargs) - widget.exec_() - return widget.get_results_back() - - -class PublishableClip: - """ - Convert a segment to publishable instance - - Args: - segment (flame.PySegment): flame api object - kwargs (optional): additional data needed for rename=True (presets) - - Returns: - flame.PySegment: flame api object - """ - vertical_clip_match = {} - marker_data = {} - types = { - "shot": "shot", - "folder": "folder", - "episode": "episode", - "sequence": "sequence", - "track": "sequence", - } - - # parents search pattern - parents_search_pattern = r"\{([a-z]*?)\}" - - # default templates for non-ui use - rename_default = False - hierarchy_default = "{_folder_}/{_sequence_}/{_track_}" - clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}" - review_track_default = "[ none ]" - base_product_name_default = "[ track name ]" - base_product_type_default = "plate" - count_from_default = 10 - count_steps_default = 10 - vertical_sync_default = False - driving_layer_default = "" - index_from_segment_default = False - use_shot_name_default = False - include_handles_default = False - retimed_handles_default = True - retimed_framerange_default = True - - def __init__(self, segment, **kwargs): - self.rename_index = kwargs["rename_index"] - self.product_type = kwargs["family"] - self.log = kwargs["log"] - - # get main parent objects - self.current_segment = segment - sequence_name = flib.get_current_sequence([segment]).name.get_value() - self.sequence_name = str(sequence_name).replace(" ", "_") - - self.clip_data = flib.get_segment_attributes(segment) - # segment (clip) main attributes - self.cs_name = self.clip_data["segment_name"] - self.cs_index = int(self.clip_data["segment"]) - self.shot_name = self.clip_data["shot_name"] - - # get track name and index - self.track_index = int(self.clip_data["track"]) - track_name = self.clip_data["track_name"] - self.track_name = str(track_name).replace(" ", "_").replace( - "*", "noname{}".format(self.track_index)) - - # adding tag.family into tag - if kwargs.get("avalon"): - self.marker_data.update(kwargs["avalon"]) - - # add publish attribute to marker data - self.marker_data.update({"publish": True}) - - # adding ui inputs if any - self.ui_inputs = kwargs.get("ui_inputs", {}) - - self.log.info("Inside of plugin: {}".format( - self.marker_data - )) - # populate default data before we get other attributes - self._populate_segment_default_data() - - # use all populated default data to create all important attributes - self._populate_attributes() - - # create parents with correct types - self._create_parents() - - def convert(self): - - # solve segment data and add them to marker data - self._convert_to_marker_data() - - # if track name is in review track name and also if driving track name - # is not in review track name: skip tag creation - if (self.track_name in self.review_layer) and ( - self.driving_layer not in self.review_layer): - return - - # deal with clip name - new_name = self.marker_data.pop("newClipName") - - if self.rename and not self.use_shot_name: - # rename segment - self.current_segment.name = str(new_name) - self.marker_data["asset"] = str(new_name) - elif self.use_shot_name: - self.marker_data["asset"] = self.shot_name - self.marker_data["hierarchyData"]["shot"] = self.shot_name - else: - self.marker_data["asset"] = self.cs_name - self.marker_data["hierarchyData"]["shot"] = self.cs_name - - if self.marker_data["heroTrack"] and self.review_layer: - self.marker_data["reviewTrack"] = self.review_layer - else: - self.marker_data["reviewTrack"] = None - - # create pype tag on track_item and add data - fpipeline.imprint(self.current_segment, self.marker_data) - - return self.current_segment - - def _populate_segment_default_data(self): - """ Populate default formatting data from segment. """ - - self.current_segment_default_data = { - "_folder_": "shots", - "_sequence_": self.sequence_name, - "_track_": self.track_name, - "_clip_": self.cs_name, - "_trackIndex_": self.track_index, - "_clipIndex_": self.cs_index - } - - def _populate_attributes(self): - """ Populate main object attributes. """ - # segment frame range and parent track name for vertical sync check - self.clip_in = int(self.clip_data["record_in"]) - self.clip_out = int(self.clip_data["record_out"]) - - # define ui inputs if non gui mode was used - self.shot_num = self.cs_index - self.log.debug( - "____ self.shot_num: {}".format(self.shot_num)) - - # ui_inputs data or default values if gui was not used - self.rename = self.ui_inputs.get( - "clipRename", {}).get("value") or self.rename_default - self.use_shot_name = self.ui_inputs.get( - "useShotName", {}).get("value") or self.use_shot_name_default - self.clip_name = self.ui_inputs.get( - "clipName", {}).get("value") or self.clip_name_default - self.hierarchy = self.ui_inputs.get( - "hierarchy", {}).get("value") or self.hierarchy_default - self.hierarchy_data = self.ui_inputs.get( - "hierarchyData", {}).get("value") or \ - self.current_segment_default_data.copy() - self.index_from_segment = self.ui_inputs.get( - "segmentIndex", {}).get("value") or self.index_from_segment_default - self.count_from = self.ui_inputs.get( - "countFrom", {}).get("value") or self.count_from_default - self.count_steps = self.ui_inputs.get( - "countSteps", {}).get("value") or self.count_steps_default - self.base_product_name = self.ui_inputs.get( - "productName", {}).get("value") or self.base_product_name_default - self.base_product_type = self.ui_inputs.get( - "productType", {}).get("value") or self.base_product_type_default - self.vertical_sync = self.ui_inputs.get( - "vSyncOn", {}).get("value") or self.vertical_sync_default - self.driving_layer = self.ui_inputs.get( - "vSyncTrack", {}).get("value") or self.driving_layer_default - self.review_track = self.ui_inputs.get( - "reviewTrack", {}).get("value") or self.review_track_default - self.audio = self.ui_inputs.get( - "audio", {}).get("value") or False - self.include_handles = self.ui_inputs.get( - "includeHandles", {}).get("value") or self.include_handles_default - self.retimed_handles = ( - self.ui_inputs.get("retimedHandles", {}).get("value") - or self.retimed_handles_default - ) - self.retimed_framerange = ( - self.ui_inputs.get("retimedFramerange", {}).get("value") - or self.retimed_framerange_default - ) - - # build product name from layer name - if self.base_product_name == "[ track name ]": - self.base_product_name = self.track_name - - # create product for publishing - self.product_name = ( - self.base_product_type + self.base_product_name.capitalize() - ) - - def _replace_hash_to_expression(self, name, text): - """ Replace hash with number in correct padding. """ - _spl = text.split("#") - _len = (len(_spl) - 1) - _repl = "{{{0}:0>{1}}}".format(name, _len) - return text.replace(("#" * _len), _repl) - - def _convert_to_marker_data(self): - """ Convert internal data to marker data. - - Populating the marker data into internal variable self.marker_data - """ - # define vertical sync attributes - hero_track = True - self.review_layer = "" - if self.vertical_sync and self.track_name not in self.driving_layer: - # if it is not then define vertical sync as None - hero_track = False - - # increasing steps by index of rename iteration - if not self.index_from_segment: - self.count_steps *= self.rename_index - - hierarchy_formatting_data = {} - hierarchy_data = deepcopy(self.hierarchy_data) - _data = self.current_segment_default_data.copy() - if self.ui_inputs: - # adding tag metadata from ui - for _k, _v in self.ui_inputs.items(): - if _v["target"] == "tag": - self.marker_data[_k] = _v["value"] - - # driving layer is set as positive match - if hero_track or self.vertical_sync: - # mark review layer - if self.review_track and ( - self.review_track not in self.review_track_default): - # if review layer is defined and not the same as default - self.review_layer = self.review_track - - # shot num calculate - if self.index_from_segment: - # use clip index from timeline - self.shot_num = self.count_steps * self.cs_index - else: - if self.rename_index == 0: - self.shot_num = self.count_from - else: - self.shot_num = self.count_from + self.count_steps - - # clip name sequence number - _data.update({"shot": self.shot_num}) - - # solve # in test to pythonic expression - for _k, _v in hierarchy_data.items(): - if "#" not in _v["value"]: - continue - hierarchy_data[ - _k]["value"] = self._replace_hash_to_expression( - _k, _v["value"]) - - # fill up pythonic expresisons in hierarchy data - for k, _v in hierarchy_data.items(): - hierarchy_formatting_data[k] = _v["value"].format(**_data) - else: - # if no gui mode then just pass default data - hierarchy_formatting_data = hierarchy_data - - tag_hierarchy_data = self._solve_tag_hierarchy_data( - hierarchy_formatting_data - ) - - tag_hierarchy_data.update({"heroTrack": True}) - if hero_track and self.vertical_sync: - self.vertical_clip_match.update({ - (self.clip_in, self.clip_out): tag_hierarchy_data - }) - - if not hero_track and self.vertical_sync: - # driving layer is set as negative match - for (_in, _out), hero_data in self.vertical_clip_match.items(): - """ - Since only one instance of hero clip is expected in - `self.vertical_clip_match`, this will loop only once - until none hero clip will be matched with hero clip. - - `tag_hierarchy_data` will be set only once for every - clip which is not hero clip. - """ - _hero_data = deepcopy(hero_data) - _hero_data.update({"heroTrack": False}) - if _in <= self.clip_in and _out >= self.clip_out: - data_product_name = hero_data["productName"] - # add track index in case duplicity of names in hero data - if self.product_name in data_product_name: - _hero_data["productName"] = self.product_name + str( - self.track_index) - # in case track name and product name is the same then add - if self.base_product_name == self.track_name: - _hero_data["productName"] = self.product_name - # assign data to return hierarchy data to tag - tag_hierarchy_data = _hero_data - break - - # add data to return data dict - self.marker_data.update(tag_hierarchy_data) - - def _solve_tag_hierarchy_data(self, hierarchy_formatting_data): - """ Solve marker data from hierarchy data and templates. """ - # fill up clip name and hierarchy keys - hierarchy_filled = self.hierarchy.format(**hierarchy_formatting_data) - clip_name_filled = self.clip_name.format(**hierarchy_formatting_data) - - # remove shot from hierarchy data: is not needed anymore - hierarchy_formatting_data.pop("shot") - - return { - "newClipName": clip_name_filled, - "hierarchy": hierarchy_filled, - "parents": self.parents, - "hierarchyData": hierarchy_formatting_data, - "productName": self.product_name, - "productType": self.base_product_type, - "families": [self.base_product_type, self.product_type] - } - - def _convert_to_entity(self, src_type, template): - """ Converting input key to key with type. """ - # convert to entity type - folder_type = self.types.get(src_type, None) - - assert folder_type, "Missing folder type for `{}`".format( - src_type - ) - - # first collect formatting data to use for formatting template - formatting_data = {} - for _k, _v in self.hierarchy_data.items(): - value = _v["value"].format( - **self.current_segment_default_data) - formatting_data[_k] = value - - return { - "folder_type": folder_type, - "entity_name": template.format( - **formatting_data - ) - } - - def _create_parents(self): - """ Create parents and return it in list. """ - self.parents = [] - - pattern = re.compile(self.parents_search_pattern) - - par_split = [(pattern.findall(t).pop(), t) - for t in self.hierarchy.split("/")] - - for type, template in par_split: - parent = self._convert_to_entity(type, template) - self.parents.append(parent) - - -# Publishing plugin functions - -# Loader plugin functions -class ClipLoader(LoaderPlugin): - """A basic clip loader for Flame - - This will implement the basic behavior for a loader to inherit from that - will containerize the reference and will implement the `remove` and - `update` logic. - - """ - log = log - - options = [ - qargparse.Boolean( - "handles", - label="Set handles", - default=0, - help="Also set handles to clip as In/Out marks" - ) - ] - - _mapping = None - _host_settings = None - - def apply_settings(cls, project_settings): - - plugin_type_settings = ( - project_settings - .get("flame", {}) - .get("load", {}) - ) - - if not plugin_type_settings: - return - - plugin_name = cls.__name__ - - plugin_settings = None - # Look for plugin settings in host specific settings - if plugin_name in plugin_type_settings: - plugin_settings = plugin_type_settings[plugin_name] - - if not plugin_settings: - return - - print(">>> We have preset for {}".format(plugin_name)) - for option, value in plugin_settings.items(): - if option == "enabled" and value is False: - print(" - is disabled by preset") - elif option == "representations": - continue - else: - print(" - setting `{}`: `{}`".format(option, value)) - setattr(cls, option, value) - - def get_colorspace(self, context): - """Get colorspace name - - Look either to version data or representation data. - - Args: - context (dict): version context data - - Returns: - str: colorspace name or None - """ - version_entity = context["version"] - version_attributes = version_entity["attrib"] - colorspace = version_attributes.get("colorSpace") - - if ( - not colorspace - or colorspace == "Unknown" - ): - colorspace = context["representation"]["data"].get( - "colorspace") - - return colorspace - - @classmethod - def get_native_colorspace(cls, input_colorspace): - """Return native colorspace name. - - Args: - input_colorspace (str | None): colorspace name - - Returns: - str: native colorspace name defined in mapping or None - """ - # TODO: rewrite to support only pipeline's remapping - if not cls._host_settings: - cls._host_settings = get_current_project_settings()["flame"] - - # [Deprecated] way of remapping - if not cls._mapping: - mapping = ( - cls._host_settings["imageio"]["profilesMapping"]["inputs"]) - cls._mapping = { - input["ocioName"]: input["flameName"] - for input in mapping - } - - native_name = cls._mapping.get(input_colorspace) - - if not native_name: - native_name = get_remapped_colorspace_to_native( - input_colorspace, "flame", cls._host_settings["imageio"]) - - return native_name - - -class OpenClipSolver(flib.MediaInfoFile): - create_new_clip = False - - log = log - - def __init__(self, openclip_file_path, feed_data, logger=None): - self.out_file = openclip_file_path - - # replace log if any - if logger: - self.log = logger - - # new feed variables: - feed_path = feed_data.pop("path") - - # initialize parent class - super(OpenClipSolver, self).__init__( - feed_path, - logger=logger - ) - - # get other metadata - self.feed_version_name = feed_data["version"] - self.feed_colorspace = feed_data.get("colorspace") - self.log.debug("feed_version_name: {}".format(self.feed_version_name)) - - # layer rename variables - self.layer_rename_template = feed_data["layer_rename_template"] - self.layer_rename_patterns = feed_data["layer_rename_patterns"] - self.context_data = feed_data["context_data"] - - # derivate other feed variables - self.feed_basename = os.path.basename(feed_path) - self.feed_dir = os.path.dirname(feed_path) - self.feed_ext = os.path.splitext(self.feed_basename)[1][1:].lower() - self.log.debug("feed_ext: {}".format(self.feed_ext)) - self.log.debug("out_file: {}".format(self.out_file)) - if not self._is_valid_tmp_file(self.out_file): - self.create_new_clip = True - - def _is_valid_tmp_file(self, file): - # check if file exists - if os.path.isfile(file): - # test also if file is not empty - with open(file) as f: - lines = f.readlines() - - if len(lines) > 2: - return True - - # file is probably corrupted - os.remove(file) - return False - - def make(self): - - if self.create_new_clip: - # New openClip - self._create_new_open_clip() - else: - self._update_open_clip() - - def _clear_handler(self, xml_object): - for handler in xml_object.findall("./handler"): - self.log.info("Handler found") - xml_object.remove(handler) - - def _create_new_open_clip(self): - self.log.info("Building new openClip") - - for tmp_xml_track in self.clip_data.iter("track"): - # solve track (layer) name - self._rename_track_name(tmp_xml_track) - - tmp_xml_feeds = tmp_xml_track.find('feeds') - tmp_xml_feeds.set('currentVersion', self.feed_version_name) - - for tmp_feed in tmp_xml_track.iter("feed"): - tmp_feed.set('vuid', self.feed_version_name) - - # add colorspace if any is set - if self.feed_colorspace: - self._add_colorspace(tmp_feed, self.feed_colorspace) - - self._clear_handler(tmp_feed) - - tmp_xml_versions_obj = self.clip_data.find('versions') - tmp_xml_versions_obj.set('currentVersion', self.feed_version_name) - for xml_new_version in tmp_xml_versions_obj: - xml_new_version.set('uid', self.feed_version_name) - xml_new_version.set('type', 'version') - - self._clear_handler(self.clip_data) - self.log.info("Adding feed version: {}".format(self.feed_basename)) - - self.write_clip_data_to_file(self.out_file, self.clip_data) - - def _get_xml_track_obj_by_uid(self, xml_data, uid): - # loop all tracks of input xml data - for xml_track in xml_data.iter("track"): - track_uid = xml_track.get("uid") - self.log.debug( - ">> track_uid:uid: {}:{}".format(track_uid, uid)) - - # get matching uids - if uid == track_uid: - return xml_track - - def _rename_track_name(self, xml_track_data): - layer_uid = xml_track_data.get("uid") - name_obj = xml_track_data.find("name") - layer_name = name_obj.text - - if ( - self.layer_rename_patterns - and not any( - re.search(lp_.lower(), layer_name.lower()) - for lp_ in self.layer_rename_patterns - ) - ): - return - - formatting_data = self._update_formatting_data( - layerName=layer_name, - layerUID=layer_uid - ) - name_obj.text = StringTemplate( - self.layer_rename_template - ).format(formatting_data) - - def _update_formatting_data(self, **kwargs): - """ Updating formatting data for layer rename - - Attributes: - key=value (optional): will be included to formatting data - as {key: value} - Returns: - dict: anatomy context data for formatting - """ - self.log.debug(">> self.clip_data: {}".format(self.clip_data)) - clip_name_obj = self.clip_data.find("name") - data = { - "originalBasename": clip_name_obj.text - } - # include version context data - data.update(self.context_data) - # include input kwargs data - data.update(kwargs) - return data - - def _update_open_clip(self): - self.log.info("Updating openClip ..") - - out_xml = ET.parse(self.out_file) - out_xml = out_xml.getroot() - - self.log.debug(">> out_xml: {}".format(out_xml)) - # loop tmp tracks - updated_any = False - for tmp_xml_track in self.clip_data.iter("track"): - # solve track (layer) name - self._rename_track_name(tmp_xml_track) - - # get tmp track uid - tmp_track_uid = tmp_xml_track.get("uid") - self.log.debug(">> tmp_track_uid: {}".format(tmp_track_uid)) - - # get out data track by uid - out_track_element = self._get_xml_track_obj_by_uid( - out_xml, tmp_track_uid) - self.log.debug( - ">> out_track_element: {}".format(out_track_element)) - - # loop tmp feeds - for tmp_xml_feed in tmp_xml_track.iter("feed"): - new_path_obj = tmp_xml_feed.find( - "spans/span/path") - new_path = new_path_obj.text - - # check if feed path already exists in track's feeds - if ( - out_track_element is not None - and self._feed_exists(out_track_element, new_path) - ): - continue - - # rename versions on feeds - tmp_xml_feed.set('vuid', self.feed_version_name) - self._clear_handler(tmp_xml_feed) - - # update fps from MediaInfoFile class - if self.fps is not None: - tmp_feed_fps_obj = tmp_xml_feed.find( - "startTimecode/rate") - tmp_feed_fps_obj.text = str(self.fps) - - # update start_frame from MediaInfoFile class - if self.start_frame is not None: - tmp_feed_nb_ticks_obj = tmp_xml_feed.find( - "startTimecode/nbTicks") - tmp_feed_nb_ticks_obj.text = str(self.start_frame) - - # update drop_mode from MediaInfoFile class - if self.drop_mode is not None: - tmp_feed_drop_mode_obj = tmp_xml_feed.find( - "startTimecode/dropMode") - tmp_feed_drop_mode_obj.text = str(self.drop_mode) - - # add colorspace if any is set - if self.feed_colorspace is not None: - self._add_colorspace(tmp_xml_feed, self.feed_colorspace) - - # then append/update feed to correct track in output - if out_track_element: - self.log.debug("updating track element ..") - # update already present track - out_feeds = out_track_element.find('feeds') - out_feeds.set('currentVersion', self.feed_version_name) - out_feeds.append(tmp_xml_feed) - - self.log.info( - "Appending new feed: {}".format( - self.feed_version_name)) - else: - self.log.debug("adding new track element ..") - # create new track as it doesn't exist yet - # set current version to feeds on tmp - tmp_xml_feeds = tmp_xml_track.find('feeds') - tmp_xml_feeds.set('currentVersion', self.feed_version_name) - out_tracks = out_xml.find("tracks") - out_tracks.append(tmp_xml_track) - - updated_any = True - - if updated_any: - # Append vUID to versions - out_xml_versions_obj = out_xml.find('versions') - out_xml_versions_obj.set( - 'currentVersion', self.feed_version_name) - new_version_obj = ET.Element( - "version", {"type": "version", "uid": self.feed_version_name}) - out_xml_versions_obj.insert(0, new_version_obj) - - self._clear_handler(out_xml) - - # fist create backup - self._create_openclip_backup_file(self.out_file) - - self.log.info("Adding feed version: {}".format( - self.feed_version_name)) - - self.write_clip_data_to_file(self.out_file, out_xml) - - self.log.debug("OpenClip Updated: {}".format(self.out_file)) - - def _feed_exists(self, xml_data, path): - # loop all available feed paths and check if - # the path is not already in file - for src_path in xml_data.iter('path'): - if path == src_path.text: - self.log.warning( - "Not appending file as it already is in .clip file") - return True - - def _create_openclip_backup_file(self, file): - bck_file = "{}.bak".format(file) - # if backup does not exist - if not os.path.isfile(bck_file): - shutil.copy2(file, bck_file) - else: - # in case it exists and is already multiplied - created = False - for _i in range(1, 99): - bck_file = "{name}.bak.{idx:0>2}".format( - name=file, - idx=_i) - # create numbered backup file - if not os.path.isfile(bck_file): - shutil.copy2(file, bck_file) - created = True - break - # in case numbered does not exists - if not created: - bck_file = "{}.bak.last".format(file) - shutil.copy2(file, bck_file) - - def _add_colorspace(self, feed_obj, profile_name): - feed_storage_obj = feed_obj.find("storageFormat") - feed_clr_obj = feed_storage_obj.find("colourSpace") - if feed_clr_obj is not None: - feed_clr_obj = ET.Element( - "colourSpace", {"type": "string"}) - feed_clr_obj.text = profile_name - feed_storage_obj.append(feed_clr_obj) diff --git a/server_addon/flame/client/ayon_flame/api/render_utils.py b/server_addon/flame/client/ayon_flame/api/render_utils.py deleted file mode 100644 index a0c77cb155..0000000000 --- a/server_addon/flame/client/ayon_flame/api/render_utils.py +++ /dev/null @@ -1,185 +0,0 @@ -import os -from xml.etree import ElementTree as ET -from ayon_core.lib import Logger - -log = Logger.get_logger(__name__) - - -def export_clip(export_path, clip, preset_path, **kwargs): - """Flame exported wrapper - - Args: - export_path (str): exporting directory path - clip (PyClip): flame api object - preset_path (str): full export path to xml file - - Kwargs: - thumb_frame_number (int)[optional]: source frame number - in_mark (int)[optional]: cut in mark - out_mark (int)[optional]: cut out mark - - Raises: - KeyError: Missing input kwarg `thumb_frame_number` - in case `thumbnail` in `export_preset` - FileExistsError: Missing export preset in shared folder - """ - import flame - - in_mark = out_mark = None - - # Set exporter - exporter = flame.PyExporter() - exporter.foreground = True - exporter.export_between_marks = True - - if kwargs.get("thumb_frame_number"): - thumb_frame_number = kwargs["thumb_frame_number"] - # make sure it exists in kwargs - if not thumb_frame_number: - raise KeyError( - "Missing key `thumb_frame_number` in input kwargs") - - in_mark = int(thumb_frame_number) - out_mark = int(thumb_frame_number) + 1 - - elif kwargs.get("in_mark") and kwargs.get("out_mark"): - in_mark = int(kwargs["in_mark"]) - out_mark = int(kwargs["out_mark"]) - else: - exporter.export_between_marks = False - - try: - # set in and out marks if they are available - if in_mark and out_mark: - clip.in_mark = in_mark - clip.out_mark = out_mark - - # export with exporter - exporter.export(clip, preset_path, export_path) - finally: - print('Exported: {} at {}-{}'.format( - clip.name.get_value(), - clip.in_mark, - clip.out_mark - )) - - -def get_preset_path_by_xml_name(xml_preset_name): - def _search_path(root): - output = [] - for root, _dirs, files in os.walk(root): - for f in files: - if f != xml_preset_name: - continue - file_path = os.path.join(root, f) - output.append(file_path) - return output - - def _validate_results(results): - if results and len(results) == 1: - return results.pop() - elif results and len(results) > 1: - print(( - "More matching presets for `{}`: /n" - "{}").format(xml_preset_name, results)) - return results.pop() - else: - return None - - from .utils import ( - get_flame_install_root, - get_flame_version - ) - - # get actual flame version and install path - _version = get_flame_version()["full"] - _install_root = get_flame_install_root() - - # search path templates - shared_search_root = "{install_root}/shared/export/presets" - install_search_root = ( - "{install_root}/presets/{version}/export/presets/flame") - - # fill templates - shared_search_root = shared_search_root.format( - install_root=_install_root - ) - install_search_root = install_search_root.format( - install_root=_install_root, - version=_version - ) - - # get search results - shared_results = _search_path(shared_search_root) - installed_results = _search_path(install_search_root) - - # first try to return shared results - shared_preset_path = _validate_results(shared_results) - - if shared_preset_path: - return os.path.dirname(shared_preset_path) - - # then try installed results - installed_preset_path = _validate_results(installed_results) - - if installed_preset_path: - return os.path.dirname(installed_preset_path) - - # 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: - _root = ET.parse(datafile) - - for key, value in data.items(): - try: - if "/" in key: - if not key.startswith("./"): - key = ".//" + key - - split_key_path = key.split("/") - element_key = split_key_path[-1] - parent_obj_path = "/".join(split_key_path[:-1]) - - parent_obj = _root.find(parent_obj_path) - element_obj = parent_obj.find(element_key) - if not element_obj: - append_element(parent_obj, element_key, value) - else: - finds = _root.findall(".//{}".format(key)) - if not finds: - raise AttributeError - for element in finds: - element.text = str(value) - except AttributeError: - log.warning( - "Cannot create attribute: {}: {}. Skipping".format( - key, value - )) - _root.write(temp_path) - - return temp_path - - -def append_element(root_element_obj, key, value): - new_element_obj = ET.Element(key) - log.debug("__ new_element_obj: {}".format(new_element_obj)) - new_element_obj.text = str(value) - root_element_obj.insert(0, new_element_obj) diff --git a/server_addon/flame/client/ayon_flame/api/scripts/wiretap_com.py b/server_addon/flame/client/ayon_flame/api/scripts/wiretap_com.py deleted file mode 100644 index 42b9257cbe..0000000000 --- a/server_addon/flame/client/ayon_flame/api/scripts/wiretap_com.py +++ /dev/null @@ -1,504 +0,0 @@ -#!/usr/bin/env python2.7 -# -*- coding: utf-8 -*- - -from __future__ import absolute_import -import os -import sys -import subprocess -import json -import xml.dom.minidom as minidom -from copy import deepcopy -import datetime -from libwiretapPythonClientAPI import ( # noqa - WireTapClientInit, - WireTapClientUninit, - WireTapNodeHandle, - WireTapServerHandle, - WireTapInt, - WireTapStr -) - - -class WireTapCom(object): - """ - Comunicator class wrapper for talking to WireTap db. - - This way we are able to set new project with settings and - correct colorspace policy. Also we are able to create new user - or get actual user with similar name (users are usually cloning - their profiles and adding date stamp into suffix). - """ - - def __init__(self, host_name=None, volume_name=None, group_name=None): - """Initialisation of WireTap communication class - - Args: - host_name (str, optional): Name of host server. Defaults to None. - volume_name (str, optional): Name of volume. Defaults to None. - group_name (str, optional): Name of user group. Defaults to None. - """ - # set main attributes of server - # if there are none set the default installation - self.host_name = host_name or "localhost" - self.volume_name = volume_name or "stonefs" - self.group_name = group_name or "staff" - - # wiretap tools dir path - self.wiretap_tools_dir = os.getenv("AYON_WIRETAP_TOOLS") - - # initialize WireTap client - WireTapClientInit() - - # add the server to shared variable - self._server = WireTapServerHandle("{}:IFFFS".format(self.host_name)) - print("WireTap connected at '{}'...".format( - self.host_name)) - - def close(self): - self._server = None - WireTapClientUninit() - print("WireTap closed...") - - def get_launch_args( - self, project_name, project_data, user_name, *args, **kwargs): - """Forming launch arguments for AYON launcher. - - Args: - project_name (str): name of project - project_data (dict): Flame compatible project data - user_name (str): name of user - - Returns: - list: arguments - """ - - workspace_name = kwargs.get("workspace_name") - color_policy = kwargs.get("color_policy") - - project_exists = self._project_prep(project_name) - if not project_exists: - self._set_project_settings(project_name, project_data) - self._set_project_colorspace(project_name, color_policy) - - user_name = self._user_prep(user_name) - - if workspace_name is None: - # default workspace - print("Using a default workspace") - return [ - "--start-project={}".format(project_name), - "--start-user={}".format(user_name), - "--create-workspace" - ] - - else: - print( - "Using a custom workspace '{}'".format(workspace_name)) - - self._workspace_prep(project_name, workspace_name) - return [ - "--start-project={}".format(project_name), - "--start-user={}".format(user_name), - "--create-workspace", - "--start-workspace={}".format(workspace_name) - ] - - def _workspace_prep(self, project_name, workspace_name): - """Preparing a workspace - - In case it doesn not exists it will create one - - Args: - project_name (str): project name - workspace_name (str): workspace name - - Raises: - AttributeError: unable to create workspace - """ - workspace_exists = self._child_is_in_parent_path( - "/projects/{}".format(project_name), workspace_name, "WORKSPACE" - ) - if not workspace_exists: - project = WireTapNodeHandle( - self._server, "/projects/{}".format(project_name)) - - workspace_node = WireTapNodeHandle() - created_workspace = project.createNode( - workspace_name, "WORKSPACE", workspace_node) - - if not created_workspace: - raise AttributeError( - "Cannot create workspace `{}` in " - "project `{}`: `{}`".format( - workspace_name, project_name, project.lastError()) - ) - - print( - "Workspace `{}` is successfully created".format(workspace_name)) - - def _project_prep(self, project_name): - """Preparing a project - - In case it doesn not exists it will create one - - Args: - project_name (str): project name - - Raises: - AttributeError: unable to create project - """ - # test if projeft exists - project_exists = self._child_is_in_parent_path( - "/projects", project_name, "PROJECT") - - if not project_exists: - volumes = self._get_all_volumes() - - if len(volumes) == 0: - raise AttributeError( - "Not able to create new project. No Volumes existing" - ) - - # check if volumes exists - if self.volume_name not in volumes: - raise AttributeError( - ("Volume '{}' does not exist in '{}'").format( - self.volume_name, volumes) - ) - - # form cmd arguments - project_create_cmd = [ - os.path.join( - self.wiretap_tools_dir, - "wiretap_create_node" - ), - '-n', - os.path.join("/volumes", self.volume_name), - '-d', - project_name, - '-g', - ] - - project_create_cmd.append(self.group_name) - - print(project_create_cmd) - - exit_code = subprocess.call( - project_create_cmd, - cwd=os.path.expanduser('~'), - preexec_fn=_subprocess_preexec_fn - ) - - if exit_code != 0: - RuntimeError("Cannot create project in flame db") - - print( - "A new project '{}' is created.".format(project_name)) - return project_exists - - def _get_all_volumes(self): - """Request all available volumens from WireTap - - Returns: - list: all available volumes in server - - Rises: - AttributeError: unable to get any volumes children from server - """ - root = WireTapNodeHandle(self._server, "/volumes") - children_num = WireTapInt(0) - - get_children_num = root.getNumChildren(children_num) - if not get_children_num: - raise AttributeError( - "Cannot get number of volumes: {}".format(root.lastError()) - ) - - volumes = [] - - # go through all children and get volume names - child_obj = WireTapNodeHandle() - for child_idx in range(children_num): - - # get a child - if not root.getChild(child_idx, child_obj): - raise AttributeError( - "Unable to get child: {}".format(root.lastError())) - - node_name = WireTapStr() - get_children_name = child_obj.getDisplayName(node_name) - - if not get_children_name: - raise AttributeError( - "Unable to get child name: {}".format( - child_obj.lastError()) - ) - - volumes.append(node_name.c_str()) - - return volumes - - def _user_prep(self, user_name): - """Ensuring user does exists in user's stack - - Args: - user_name (str): name of a user - - Raises: - AttributeError: unable to create user - """ - - # get all used usernames in db - used_names = self._get_usernames() - print(">> used_names: {}".format(used_names)) - - # filter only those which are sharing input user name - filtered_users = [user for user in used_names if user_name in user] - - if filtered_users: - # TODO: need to find lastly created following regex pattern for - # date used in name - return filtered_users.pop() - - # create new user name with date in suffix - now = datetime.datetime.now() # current date and time - date = now.strftime("%Y%m%d") - new_user_name = "{}_{}".format(user_name, date) - print(new_user_name) - - if not self._child_is_in_parent_path("/users", new_user_name, "USER"): - # Create the new user - users = WireTapNodeHandle(self._server, "/users") - - user_node = WireTapNodeHandle() - created_user = users.createNode(new_user_name, "USER", user_node) - if not created_user: - raise AttributeError( - "User {} cannot be created: {}".format( - new_user_name, users.lastError()) - ) - - print("User `{}` is created".format(new_user_name)) - return new_user_name - - def _get_usernames(self): - """Requesting all available users from WireTap - - Returns: - list: all available user names - - Raises: - AttributeError: there are no users in server - """ - root = WireTapNodeHandle(self._server, "/users") - children_num = WireTapInt(0) - - get_children_num = root.getNumChildren(children_num) - if not get_children_num: - raise AttributeError( - "Cannot get number of volumes: {}".format(root.lastError()) - ) - - usernames = [] - - # go through all children and get volume names - child_obj = WireTapNodeHandle() - for child_idx in range(children_num): - - # get a child - if not root.getChild(child_idx, child_obj): - raise AttributeError( - "Unable to get child: {}".format(root.lastError())) - - node_name = WireTapStr() - get_children_name = child_obj.getDisplayName(node_name) - - if not get_children_name: - raise AttributeError( - "Unable to get child name: {}".format( - child_obj.lastError()) - ) - - usernames.append(node_name.c_str()) - - return usernames - - def _child_is_in_parent_path(self, parent_path, child_name, child_type): - """Checking if a given child is in parent path. - - Args: - parent_path (str): db path to parent - child_name (str): name of child - child_type (str): type of child - - Raises: - AttributeError: Not able to get number of children - AttributeError: Not able to get children form parent - AttributeError: Not able to get children name - AttributeError: Not able to get children type - - Returns: - bool: True if child is in parent path - """ - parent = WireTapNodeHandle(self._server, parent_path) - - # iterate number of children - children_num = WireTapInt(0) - requested = parent.getNumChildren(children_num) - if not requested: - raise AttributeError(( - "Error: Cannot request number of " - "children from the node {}. Make sure your " - "wiretap service is running: {}").format( - parent_path, parent.lastError()) - ) - - # iterate children - child_obj = WireTapNodeHandle() - for child_idx in range(children_num): - if not parent.getChild(child_idx, child_obj): - raise AttributeError( - "Cannot get child: {}".format( - parent.lastError())) - - node_name = WireTapStr() - node_type = WireTapStr() - - if not child_obj.getDisplayName(node_name): - raise AttributeError( - "Unable to get child name: %s" % child_obj.lastError() - ) - if not child_obj.getNodeTypeStr(node_type): - raise AttributeError( - "Unable to obtain child type: %s" % child_obj.lastError() - ) - - if (node_name.c_str() == child_name) and ( - node_type.c_str() == child_type): - return True - - return False - - def _set_project_settings(self, project_name, project_data): - """Setting project attributes. - - Args: - project_name (str): name of project - project_data (dict): data with project attributes - (flame compatible) - - Raises: - AttributeError: Not able to set project attributes - """ - # generated xml from project_data dict - _xml = "" - for key, value in project_data.items(): - _xml += "<{}>{}".format(key, value, key) - _xml += "" - - pretty_xml = minidom.parseString(_xml).toprettyxml() - print("__ xml: {}".format(pretty_xml)) - - # set project data to wiretap - project_node = WireTapNodeHandle( - self._server, "/projects/{}".format(project_name)) - - if not project_node.setMetaData("XML", _xml): - raise AttributeError( - "Not able to set project attributes {}. Error: {}".format( - project_name, project_node.lastError()) - ) - - print("Project settings successfully set.") - - def _set_project_colorspace(self, project_name, color_policy): - """Set project's colorspace policy. - - Args: - project_name (str): name of project - color_policy (str): name of policy - - Raises: - RuntimeError: Not able to set colorspace policy - """ - color_policy = color_policy or "Legacy" - - # check if the colour policy in custom dir - if "/" in color_policy: - # if unlikelly full path was used make it redundant - color_policy = color_policy.replace("/syncolor/policies/", "") - # expecting input is `Shared/NameOfPolicy` - color_policy = "/syncolor/policies/{}".format( - color_policy) - else: - color_policy = "/syncolor/policies/Autodesk/{}".format( - color_policy) - - # create arguments - project_colorspace_cmd = [ - os.path.join( - self.wiretap_tools_dir, - "wiretap_duplicate_node" - ), - "-s", - color_policy, - "-n", - "/projects/{}/syncolor".format(project_name) - ] - - print(project_colorspace_cmd) - - exit_code = subprocess.call( - project_colorspace_cmd, - cwd=os.path.expanduser('~'), - preexec_fn=_subprocess_preexec_fn - ) - - if exit_code != 0: - RuntimeError("Cannot set colorspace {} on project {}".format( - color_policy, project_name - )) - - -def _subprocess_preexec_fn(): - """ Helper function - - Setting permission mask to 0777 - """ - os.setpgrp() - os.umask(0o000) - - -if __name__ == "__main__": - # get json exchange data - json_path = sys.argv[-1] - json_data = open(json_path).read() - in_data = json.loads(json_data) - out_data = deepcopy(in_data) - - # get main server attributes - host_name = in_data.pop("host_name") - volume_name = in_data.pop("volume_name") - group_name = in_data.pop("group_name") - - # initialize class - wiretap_handler = WireTapCom(host_name, volume_name, group_name) - - try: - app_args = wiretap_handler.get_launch_args( - project_name=in_data.pop("project_name"), - project_data=in_data.pop("project_data"), - user_name=in_data.pop("user_name"), - **in_data - ) - finally: - wiretap_handler.close() - - # set returned args back to out data - out_data.update({ - "app_args": app_args - }) - - # write it out back to the exchange json file - with open(json_path, "w") as file_stream: - json.dump(out_data, file_stream, indent=4) diff --git a/server_addon/flame/client/ayon_flame/api/utils.py b/server_addon/flame/client/ayon_flame/api/utils.py deleted file mode 100644 index 03a694c25c..0000000000 --- a/server_addon/flame/client/ayon_flame/api/utils.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -Flame utils for syncing scripts -""" - -import os -import shutil -from ayon_core.lib import Logger -from ayon_flame import FLAME_ADDON_ROOT - -log = Logger.get_logger(__name__) - - -def _sync_utility_scripts(env=None): - """ Synchronizing basic utlility scripts for flame. - - To be able to run start AYON within Flame we have to copy - all utility_scripts and additional FLAME_SCRIPT_DIR into - `/opt/Autodesk/shared/python`. This will be always synchronizing those - folders. - """ - - env = env or os.environ - - # initiate inputs - scripts = {} - fsd_env = env.get("FLAME_SCRIPT_DIRS", "") - flame_shared_dir = "/opt/Autodesk/shared/python" - - fsd_paths = [os.path.join( - FLAME_ADDON_ROOT, - "api", - "utility_scripts" - )] - - # collect script dirs - log.info("FLAME_SCRIPT_DIRS: `{fsd_env}`".format(**locals())) - log.info("fsd_paths: `{fsd_paths}`".format(**locals())) - - # add application environment setting for FLAME_SCRIPT_DIR - # to script path search - for _dirpath in fsd_env.split(os.pathsep): - if not os.path.isdir(_dirpath): - log.warning("Path is not a valid dir: `{_dirpath}`".format( - **locals())) - continue - fsd_paths.append(_dirpath) - - # collect scripts from dirs - for path in fsd_paths: - scripts.update({path: os.listdir(path)}) - - remove_black_list = [] - for _k, s_list in scripts.items(): - remove_black_list += s_list - - log.info("remove_black_list: `{remove_black_list}`".format(**locals())) - log.info("Additional Flame script paths: `{fsd_paths}`".format(**locals())) - log.info("Flame Scripts: `{scripts}`".format(**locals())) - - # make sure no script file is in folder - if next(iter(os.listdir(flame_shared_dir)), None): - for _itm in os.listdir(flame_shared_dir): - skip = False - - # skip all scripts and folders which are not maintained - if _itm not in remove_black_list: - skip = True - - # do not skip if pyc in extension - if not os.path.isdir(_itm) and "pyc" in os.path.splitext(_itm)[-1]: - skip = False - - # continue if skip in true - if skip: - continue - - path = os.path.join(flame_shared_dir, _itm) - log.info("Removing `{path}`...".format(**locals())) - - try: - if os.path.isdir(path): - shutil.rmtree(path, onerror=None) - else: - os.remove(path) - except PermissionError as msg: - log.warning( - "Not able to remove: `{}`, Problem with: `{}`".format( - path, - msg - ) - ) - - # copy scripts into Resolve's utility scripts dir - for dirpath, scriptlist in scripts.items(): - # directory and scripts list - for _script in scriptlist: - # script in script list - src = os.path.join(dirpath, _script) - dst = os.path.join(flame_shared_dir, _script) - log.info("Copying `{src}` to `{dst}`...".format(**locals())) - - try: - if os.path.isdir(src): - shutil.copytree( - src, dst, symlinks=False, - ignore=None, ignore_dangling_symlinks=False - ) - else: - shutil.copy2(src, dst) - except (PermissionError, FileExistsError) as msg: - log.warning( - "Not able to copy to: `{}`, Problem with: `{}`".format( - dst, - msg - ) - ) - - -def setup(env=None): - """ Wrapper installer started from - `flame/hooks/pre_flame_setup.py` - """ - env = env or os.environ - - # synchronize resolve utility scripts - _sync_utility_scripts(env) - - log.info("Flame AYON wrapper has been installed") - - -def get_flame_version(): - import flame - - return { - "full": flame.get_version(), - "major": flame.get_version_major(), - "minor": flame.get_version_minor(), - "patch": flame.get_version_patch() - } - - -def get_flame_install_root(): - return "/opt/Autodesk" diff --git a/server_addon/flame/client/ayon_flame/api/workio.py b/server_addon/flame/client/ayon_flame/api/workio.py deleted file mode 100644 index eef10a4847..0000000000 --- a/server_addon/flame/client/ayon_flame/api/workio.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Host API required Work Files tool""" - -import os -from ayon_core.lib import Logger -# from .. import ( -# get_project_manager, -# get_current_project -# ) - - -log = Logger.get_logger(__name__) - -exported_projet_ext = ".otoc" - - -def file_extensions(): - return [exported_projet_ext] - - -def has_unsaved_changes(): - pass - - -def save_file(filepath): - pass - - -def open_file(filepath): - pass - - -def current_file(): - pass - - -def work_root(session): - return os.path.normpath(session["AYON_WORKDIR"]).replace("\\", "/") diff --git a/server_addon/flame/client/ayon_flame/hooks/pre_flame_setup.py b/server_addon/flame/client/ayon_flame/hooks/pre_flame_setup.py deleted file mode 100644 index e9e9aca3f4..0000000000 --- a/server_addon/flame/client/ayon_flame/hooks/pre_flame_setup.py +++ /dev/null @@ -1,239 +0,0 @@ -import os -import json -import tempfile -import contextlib -import socket -from pprint import pformat - -from ayon_core.lib import ( - get_ayon_username, - run_subprocess, -) -from ayon_applications import PreLaunchHook, LaunchTypes -from ayon_flame import FLAME_ADDON_ROOT - - -class FlamePrelaunch(PreLaunchHook): - """ Flame prelaunch hook - - Will make sure flame_script_dirs are copied to user's folder defined - in environment var FLAME_SCRIPT_DIR. - """ - app_groups = {"flame"} - permissions = 0o777 - - wtc_script_path = os.path.join( - FLAME_ADDON_ROOT, "api", "scripts", "wiretap_com.py" - ) - launch_types = {LaunchTypes.local} - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.signature = "( {} )".format(self.__class__.__name__) - - def execute(self): - _env = self.launch_context.env - self.flame_python_exe = _env["AYON_FLAME_PYTHON_EXEC"] - self.flame_pythonpath = _env["AYON_FLAME_PYTHONPATH"] - - """Hook entry method.""" - project_entity = self.data["project_entity"] - project_name = project_entity["name"] - volume_name = _env.get("FLAME_WIRETAP_VOLUME") - - # get image io - project_settings = self.data["project_settings"] - - imageio_flame = project_settings["flame"]["imageio"] - - # Check whether 'enabled' key from host imageio settings exists - # so we can tell if host is using the new colormanagement framework. - # If the 'enabled' isn't found we want 'colormanaged' set to True - # because prior to the key existing we always did colormanagement for - # Flame - colormanaged = imageio_flame.get("enabled") - # if key was not found, set to True - # ensuring backward compatibility - if colormanaged is None: - colormanaged = True - - # get user name and host name - user_name = get_ayon_username() - user_name = user_name.replace(".", "_") - - hostname = socket.gethostname() # not returning wiretap host name - - self.log.debug("Collected user \"{}\"".format(user_name)) - self.log.info(pformat(project_entity)) - project_attribs = project_entity["attrib"] - width = project_attribs["resolutionWidth"] - height = project_attribs["resolutionHeight"] - fps = float(project_attribs["fps"]) - - project_data = { - "Name": project_entity["name"], - "Nickname": project_entity["code"], - "Description": "Created by AYON", - "SetupDir": project_entity["name"], - "FrameWidth": int(width), - "FrameHeight": int(height), - "AspectRatio": float( - (width / height) * project_attribs["pixelAspect"] - ), - "FrameRate": self._get_flame_fps(fps) - } - - data_to_script = { - # from settings - "host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname, - "volume_name": volume_name, - "group_name": _env.get("FLAME_WIRETAP_GROUP"), - - # from project - "project_name": project_name, - "user_name": user_name, - "project_data": project_data - } - - # add color management data - if colormanaged: - project_data.update({ - "FrameDepth": str(imageio_flame["project"]["frameDepth"]), - "FieldDominance": str( - imageio_flame["project"]["fieldDominance"]) - }) - data_to_script["color_policy"] = str( - imageio_flame["project"]["colourPolicy"]) - - self.log.info(pformat(dict(_env))) - self.log.info(pformat(data_to_script)) - - # add to python path from settings - self._add_pythonpath() - - app_arguments = self._get_launch_arguments(data_to_script) - - # fix project data permission issue - self._fix_permissions(project_name, volume_name) - - self.launch_context.launch_args.extend(app_arguments) - - def _fix_permissions(self, project_name, volume_name): - """Work around for project data permissions - - Reported issue: when project is created locally on one machine, - it is impossible to migrate it to other machine. Autodesk Flame - is crating some unmanagable files which needs to be opened to 0o777. - - Args: - project_name (str): project name - volume_name (str): studio volume - """ - dirs_to_modify = [ - "/usr/discreet/project/{}".format(project_name), - "/opt/Autodesk/clip/{}/{}.prj".format(volume_name, project_name), - "/usr/discreet/clip/{}/{}.prj".format(volume_name, project_name) - ] - - for dirtm in dirs_to_modify: - for root, dirs, files in os.walk(dirtm): - try: - for name in set(dirs) | set(files): - path = os.path.join(root, name) - st = os.stat(path) - if oct(st.st_mode) != self.permissions: - os.chmod(path, self.permissions) - - except OSError as exc: - self.log.warning("Not able to open files: {}".format(exc)) - - 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") - - # separate it explicitly by `;` that is what we use in settings - new_pythonpath = self.flame_pythonpath.split(os.pathsep) - new_pythonpath += pythonpath.split(os.pathsep) - - self.launch_context.env["PYTHONPATH"] = os.pathsep.join(new_pythonpath) - - def _get_launch_arguments(self, script_data): - # Dump data to string - dumped_script_data = json.dumps(script_data) - - with make_temp_file(dumped_script_data) as tmp_json_path: - # Prepare subprocess arguments - args = [ - self.flame_python_exe.format( - **self.launch_context.env - ), - self.wtc_script_path, - tmp_json_path - ] - self.log.info("Executing: {}".format(" ".join(args))) - - process_kwargs = { - "logger": self.log, - "env": self.launch_context.env - } - - run_subprocess(args, **process_kwargs) - - # process returned json file to pass launch args - return_json_data = open(tmp_json_path).read() - returned_data = json.loads(return_json_data) - app_args = returned_data.get("app_args") - self.log.info("____ app_args: `{}`".format(app_args)) - - if not app_args: - RuntimeError("App arguments were not solved") - - return app_args - - -@contextlib.contextmanager -def make_temp_file(data): - try: - # Store dumped json to temporary file - temporary_json_file = tempfile.NamedTemporaryFile( - mode="w", suffix=".json", delete=False - ) - temporary_json_file.write(data) - temporary_json_file.close() - temporary_json_filepath = temporary_json_file.name.replace( - "\\", "/" - ) - - yield temporary_json_filepath - - except IOError as _error: - raise IOError( - "Not able to create temp json file: {}".format( - _error - ) - ) - - finally: - # Remove the temporary json - os.remove(temporary_json_filepath) diff --git a/server_addon/flame/client/ayon_flame/otio/__init__.py b/server_addon/flame/client/ayon_flame/otio/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/server_addon/flame/client/ayon_flame/otio/flame_export.py b/server_addon/flame/client/ayon_flame/otio/flame_export.py deleted file mode 100644 index bebe9be1c1..0000000000 --- a/server_addon/flame/client/ayon_flame/otio/flame_export.py +++ /dev/null @@ -1,624 +0,0 @@ -""" compatibility OpenTimelineIO 0.12.0 and newer -""" - -import os -import re -import json -import logging -import opentimelineio as otio -from . import utils - -import flame -from pprint import pformat - -log = logging.getLogger(__name__) - - -TRACK_TYPES = { - "video": otio.schema.TrackKind.Video, - "audio": otio.schema.TrackKind.Audio -} -MARKERS_COLOR_MAP = { - (1.0, 0.0, 0.0): otio.schema.MarkerColor.RED, - (1.0, 0.5, 0.0): otio.schema.MarkerColor.ORANGE, - (1.0, 1.0, 0.0): otio.schema.MarkerColor.YELLOW, - (1.0, 0.5, 1.0): otio.schema.MarkerColor.PINK, - (1.0, 1.0, 1.0): otio.schema.MarkerColor.WHITE, - (0.0, 1.0, 0.0): otio.schema.MarkerColor.GREEN, - (0.0, 1.0, 1.0): otio.schema.MarkerColor.CYAN, - (0.0, 0.0, 1.0): otio.schema.MarkerColor.BLUE, - (0.5, 0.0, 0.5): otio.schema.MarkerColor.PURPLE, - (0.5, 0.0, 1.0): otio.schema.MarkerColor.MAGENTA, - (0.0, 0.0, 0.0): otio.schema.MarkerColor.BLACK -} -MARKERS_INCLUDE = True - - -class CTX: - _fps = None - _tl_start_frame = None - project = None - clips = None - - @classmethod - def set_fps(cls, new_fps): - if not isinstance(new_fps, float): - raise TypeError("Invalid fps type {}".format(type(new_fps))) - if cls._fps != new_fps: - cls._fps = new_fps - - @classmethod - def get_fps(cls): - return cls._fps - - @classmethod - def set_tl_start_frame(cls, number): - if not isinstance(number, int): - raise TypeError("Invalid timeline start frame type {}".format( - type(number))) - if cls._tl_start_frame != number: - cls._tl_start_frame = number - - @classmethod - def get_tl_start_frame(cls): - return cls._tl_start_frame - - -def flatten(_list): - for item in _list: - if isinstance(item, (list, tuple)): - for sub_item in flatten(item): - yield sub_item - else: - yield item - - -def get_current_flame_project(): - project = flame.project.current_project - return project - - -def create_otio_rational_time(frame, fps): - return otio.opentime.RationalTime( - float(frame), - float(fps) - ) - - -def create_otio_time_range(start_frame, frame_duration, fps): - return otio.opentime.TimeRange( - start_time=create_otio_rational_time(start_frame, fps), - duration=create_otio_rational_time(frame_duration, fps) - ) - - -def _get_metadata(item): - if hasattr(item, 'metadata'): - return dict(item.metadata) if item.metadata else {} - return {} - - -def create_time_effects(otio_clip, speed): - otio_effect = None - - # retime on track item - if speed != 1.: - # make effect - otio_effect = otio.schema.LinearTimeWarp() - otio_effect.name = "Speed" - otio_effect.time_scalar = speed - otio_effect.metadata = {} - - # freeze frame effect - if speed == 0.: - otio_effect = otio.schema.FreezeFrame() - otio_effect.name = "FreezeFrame" - otio_effect.metadata = {} - - if otio_effect: - # add otio effect to clip effects - otio_clip.effects.append(otio_effect) - - -def _get_marker_color(flame_colour): - # clamp colors to closes half numbers - _flame_colour = [ - (lambda x: round(x * 2) / 2)(c) - for c in flame_colour] - - for color, otio_color_type in MARKERS_COLOR_MAP.items(): - if _flame_colour == list(color): - return otio_color_type - - return otio.schema.MarkerColor.RED - - -def _get_flame_markers(item): - output_markers = [] - - time_in = item.record_in.relative_frame - - for marker in item.markers: - log.debug(marker) - start_frame = marker.location.get_value().relative_frame - - start_frame = (start_frame - time_in) + 1 - - marker_data = { - "name": marker.name.get_value(), - "duration": marker.duration.get_value().relative_frame, - "comment": marker.comment.get_value(), - "start_frame": start_frame, - "colour": marker.colour.get_value() - } - - output_markers.append(marker_data) - - return output_markers - - -def create_otio_markers(otio_item, item): - markers = _get_flame_markers(item) - for marker in markers: - frame_rate = CTX.get_fps() - - marked_range = otio.opentime.TimeRange( - start_time=otio.opentime.RationalTime( - marker["start_frame"], - frame_rate - ), - duration=otio.opentime.RationalTime( - marker["duration"], - frame_rate - ) - ) - - # testing the comment if it is not containing json string - check_if_json = re.findall( - re.compile(r"[{:}]"), - marker["comment"] - ) - - # to identify this as json, at least 3 items in the list should - # be present ["{", ":", "}"] - metadata = {} - if len(check_if_json) >= 3: - # this is json string - try: - # capture exceptions which are related to strings only - metadata.update( - json.loads(marker["comment"]) - ) - except ValueError as msg: - log.error("Marker json conversion: {}".format(msg)) - else: - metadata["comment"] = marker["comment"] - - otio_marker = otio.schema.Marker( - name=marker["name"], - color=_get_marker_color( - marker["colour"]), - marked_range=marked_range, - metadata=metadata - ) - - otio_item.markers.append(otio_marker) - - -def create_otio_reference(clip_data, fps=None): - metadata = _get_metadata(clip_data) - duration = int(clip_data["source_duration"]) - - # get file info for path and start frame - frame_start = 0 - fps = fps or CTX.get_fps() - - path = clip_data["fpath"] - - file_name = os.path.basename(path) - file_head, extension = os.path.splitext(file_name) - - # get padding and other file infos - log.debug("_ path: {}".format(path)) - - otio_ex_ref_item = None - - is_sequence = frame_number = utils.get_frame_from_filename(file_name) - if is_sequence: - file_head = file_name.split(frame_number)[:-1] - frame_start = int(frame_number) - padding = len(frame_number) - - metadata.update({ - "isSequence": True, - "padding": padding - }) - - # if it is file sequence try to create `ImageSequenceReference` - # the OTIO might not be compatible so return nothing and do it old way - try: - dirname = os.path.dirname(path) - otio_ex_ref_item = otio.schema.ImageSequenceReference( - target_url_base=dirname + os.sep, - name_prefix=file_head, - name_suffix=extension, - start_frame=frame_start, - frame_zero_padding=padding, - rate=fps, - available_range=create_otio_time_range( - frame_start, - duration, - fps - ) - ) - except AttributeError: - pass - - if not otio_ex_ref_item: - dirname, file_name = os.path.split(path) - file_name = utils.get_reformatted_filename(file_name, padded=False) - reformated_path = os.path.join(dirname, file_name) - # in case old OTIO or video file create `ExternalReference` - otio_ex_ref_item = otio.schema.ExternalReference( - target_url=reformated_path, - available_range=create_otio_time_range( - frame_start, - duration, - fps - ) - ) - - # add metadata to otio item - add_otio_metadata(otio_ex_ref_item, clip_data, **metadata) - - return otio_ex_ref_item - - -def create_otio_clip(clip_data): - from ayon_flame.api import MediaInfoFile, TimeEffectMetadata - - segment = clip_data["PySegment"] - - # calculate source in - media_info = MediaInfoFile(clip_data["fpath"], logger=log) - media_timecode_start = media_info.start_frame - media_fps = media_info.fps - - # Timewarp metadata - tw_data = TimeEffectMetadata(segment, logger=log).data - log.debug("__ tw_data: {}".format(tw_data)) - - # define first frame - file_first_frame = utils.get_frame_from_filename( - clip_data["fpath"]) - if file_first_frame: - file_first_frame = int(file_first_frame) - - first_frame = media_timecode_start or file_first_frame or 0 - - _clip_source_in = int(clip_data["source_in"]) - _clip_source_out = int(clip_data["source_out"]) - _clip_record_in = clip_data["record_in"] - _clip_record_out = clip_data["record_out"] - _clip_record_duration = int(clip_data["record_duration"]) - - log.debug("_ file_first_frame: {}".format(file_first_frame)) - log.debug("_ first_frame: {}".format(first_frame)) - log.debug("_ _clip_source_in: {}".format(_clip_source_in)) - log.debug("_ _clip_source_out: {}".format(_clip_source_out)) - log.debug("_ _clip_record_in: {}".format(_clip_record_in)) - log.debug("_ _clip_record_out: {}".format(_clip_record_out)) - - # first solve if the reverse timing - speed = 1 - if clip_data["source_in"] > clip_data["source_out"]: - source_in = _clip_source_out - int(first_frame) - source_out = _clip_source_in - int(first_frame) - speed = -1 - else: - source_in = _clip_source_in - int(first_frame) - source_out = _clip_source_out - int(first_frame) - - log.debug("_ source_in: {}".format(source_in)) - log.debug("_ source_out: {}".format(source_out)) - - if file_first_frame: - log.debug("_ file_source_in: {}".format( - file_first_frame + source_in)) - log.debug("_ file_source_in: {}".format( - file_first_frame + source_out)) - - source_duration = (source_out - source_in + 1) - - # secondly check if any change of speed - if source_duration != _clip_record_duration: - retime_speed = float(source_duration) / float(_clip_record_duration) - log.debug("_ calculated speed: {}".format(retime_speed)) - speed *= retime_speed - - # get speed from metadata if available - if tw_data.get("speed"): - speed = tw_data["speed"] - log.debug("_ metadata speed: {}".format(speed)) - - log.debug("_ speed: {}".format(speed)) - log.debug("_ source_duration: {}".format(source_duration)) - log.debug("_ _clip_record_duration: {}".format(_clip_record_duration)) - - # create media reference - media_reference = create_otio_reference( - clip_data, media_fps) - - # creatae source range - source_range = create_otio_time_range( - source_in, - _clip_record_duration, - CTX.get_fps() - ) - - otio_clip = otio.schema.Clip( - name=clip_data["segment_name"], - source_range=source_range, - media_reference=media_reference - ) - - # Add markers - if MARKERS_INCLUDE: - create_otio_markers(otio_clip, segment) - - if speed != 1: - create_time_effects(otio_clip, speed) - - return otio_clip - - -def create_otio_gap(gap_start, clip_start, tl_start_frame, fps): - return otio.schema.Gap( - source_range=create_otio_time_range( - gap_start, - (clip_start - tl_start_frame) - gap_start, - fps - ) - ) - - -def _get_colourspace_policy(): - - output = {} - # get policies project path - policy_dir = "/opt/Autodesk/project/{}/synColor/policy".format( - CTX.project.name - ) - log.debug(policy_dir) - policy_fp = os.path.join(policy_dir, "policy.cfg") - - if not os.path.exists(policy_fp): - return output - - with open(policy_fp) as file: - dict_conf = dict(line.strip().split(' = ', 1) for line in file) - output.update( - {"openpype.flame.{}".format(k): v for k, v in dict_conf.items()} - ) - return output - - -def _create_otio_timeline(sequence): - - metadata = _get_metadata(sequence) - - # find colour policy files and add them to metadata - colorspace_policy = _get_colourspace_policy() - metadata.update(colorspace_policy) - - metadata.update({ - "openpype.timeline.width": int(sequence.width), - "openpype.timeline.height": int(sequence.height), - "openpype.timeline.pixelAspect": 1 - }) - - rt_start_time = create_otio_rational_time( - CTX.get_tl_start_frame(), CTX.get_fps()) - - return otio.schema.Timeline( - name=str(sequence.name)[1:-1], - global_start_time=rt_start_time, - metadata=metadata - ) - - -def create_otio_track(track_type, track_name): - return otio.schema.Track( - name=track_name, - kind=TRACK_TYPES[track_type] - ) - - -def add_otio_gap(clip_data, otio_track, prev_out): - gap_length = clip_data["record_in"] - prev_out - if prev_out != 0: - gap_length -= 1 - - gap = otio.opentime.TimeRange( - duration=otio.opentime.RationalTime( - gap_length, - CTX.get_fps() - ) - ) - otio_gap = otio.schema.Gap(source_range=gap) - otio_track.append(otio_gap) - - -def add_otio_metadata(otio_item, item, **kwargs): - metadata = _get_metadata(item) - - # add additional metadata from kwargs - if kwargs: - metadata.update(kwargs) - - # add metadata to otio item metadata - for key, value in metadata.items(): - otio_item.metadata.update({key: value}) - - -def _get_shot_tokens_values(clip, tokens): - old_value = None - output = {} - - old_value = clip.shot_name.get_value() - - for token in tokens: - clip.shot_name.set_value(token) - _key = re.sub("[ <>]", "", token) - - try: - output[_key] = int(clip.shot_name.get_value()) - except ValueError: - output[_key] = clip.shot_name.get_value() - - clip.shot_name.set_value(old_value) - - return output - - -def _get_segment_attributes(segment): - - log.debug("Segment name|hidden: {}|{}".format( - segment.name.get_value(), segment.hidden - )) - if ( - segment.name.get_value() == "" - or segment.hidden.get_value() - ): - return None - - # Add timeline segment to tree - clip_data = { - "segment_name": segment.name.get_value(), - "segment_comment": segment.comment.get_value(), - "shot_name": segment.shot_name.get_value(), - "tape_name": segment.tape_name, - "source_name": segment.source_name, - "fpath": segment.file_path, - "PySegment": segment - } - - # add all available shot tokens - shot_tokens = _get_shot_tokens_values( - segment, - ["", "", "", ""] - ) - clip_data.update(shot_tokens) - - # populate shot source metadata - segment_attrs = [ - "record_duration", "record_in", "record_out", - "source_duration", "source_in", "source_out" - ] - segment_attrs_data = {} - for attr in segment_attrs: - if not hasattr(segment, attr): - continue - _value = getattr(segment, attr) - segment_attrs_data[attr] = str(_value).replace("+", ":") - - if attr in ["record_in", "record_out"]: - clip_data[attr] = _value.relative_frame - else: - clip_data[attr] = _value.frame - - clip_data["segment_timecodes"] = segment_attrs_data - - return clip_data - - -def create_otio_timeline(sequence): - log.info(dir(sequence)) - log.info(sequence.attributes) - - CTX.project = get_current_flame_project() - - # get current timeline - CTX.set_fps( - float(str(sequence.frame_rate)[:-4])) - - tl_start_frame = utils.timecode_to_frames( - str(sequence.start_time).replace("+", ":"), - CTX.get_fps() - ) - CTX.set_tl_start_frame(tl_start_frame) - - # convert timeline to otio - otio_timeline = _create_otio_timeline(sequence) - - # create otio tracks and clips - for ver in sequence.versions: - for track in ver.tracks: - # avoid all empty tracks - # or hidden tracks - if ( - len(track.segments) == 0 - or track.hidden.get_value() - ): - continue - - # convert track to otio - otio_track = create_otio_track( - "video", str(track.name)[1:-1]) - - all_segments = [] - for segment in track.segments: - clip_data = _get_segment_attributes(segment) - if not clip_data: - continue - all_segments.append(clip_data) - - segments_ordered = dict(enumerate(all_segments)) - log.debug("_ segments_ordered: {}".format( - pformat(segments_ordered) - )) - if not segments_ordered: - continue - - for itemindex, segment_data in segments_ordered.items(): - log.debug("_ itemindex: {}".format(itemindex)) - - # Add Gap if needed - prev_item = ( - segment_data - if itemindex == 0 - else segments_ordered[itemindex - 1] - ) - log.debug("_ segment_data: {}".format(segment_data)) - - # calculate clip frame range difference from each other - clip_diff = segment_data["record_in"] - prev_item["record_out"] - - # add gap if first track item is not starting - # at first timeline frame - if itemindex == 0 and segment_data["record_in"] > 0: - add_otio_gap(segment_data, otio_track, 0) - - # or add gap if following track items are having - # frame range differences from each other - elif itemindex and clip_diff != 1: - add_otio_gap( - segment_data, otio_track, prev_item["record_out"]) - - # create otio clip and add it to track - otio_clip = create_otio_clip(segment_data) - otio_track.append(otio_clip) - - log.debug("_ otio_clip: {}".format(otio_clip)) - - # create otio marker - # create otio metadata - - # add track to otio timeline - otio_timeline.tracks.append(otio_track) - - return otio_timeline - - -def write_to_file(otio_timeline, path): - otio.adapters.write_to_file(otio_timeline, path) diff --git a/server_addon/flame/client/ayon_flame/otio/utils.py b/server_addon/flame/client/ayon_flame/otio/utils.py deleted file mode 100644 index 5a28263fc2..0000000000 --- a/server_addon/flame/client/ayon_flame/otio/utils.py +++ /dev/null @@ -1,91 +0,0 @@ -import re -import opentimelineio as otio -import logging -log = logging.getLogger(__name__) - -FRAME_PATTERN = re.compile(r"[\._](\d+)[\.]") - - -def timecode_to_frames(timecode, framerate): - rt = otio.opentime.from_timecode(timecode, framerate) - return int(otio.opentime.to_frames(rt)) - - -def frames_to_timecode(frames, framerate): - rt = otio.opentime.from_frames(frames, framerate) - return otio.opentime.to_timecode(rt) - - -def frames_to_seconds(frames, framerate): - rt = otio.opentime.from_frames(frames, framerate) - return otio.opentime.to_seconds(rt) - - -def get_reformatted_filename(filename, padded=True): - """ - Return fixed python expression path - - Args: - filename (str): file name - - Returns: - type: string with reformatted path - - Example: - get_reformatted_filename("plate.1001.exr") > plate.%04d.exr - - """ - found = FRAME_PATTERN.search(filename) - - if not found: - log.info("File name is not sequence: {}".format(filename)) - return filename - - padding = get_padding_from_filename(filename) - - 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_filename(filename): - """ - Return padding number from Flame path style - - Args: - filename (str): file name - - Returns: - int: padding number - - Example: - get_padding_from_filename("plate.0001.exr") > 4 - - """ - found = get_frame_from_filename(filename) - - return len(found) if found else None - - -def get_frame_from_filename(filename): - """ - Return sequence number from Flame path style - - Args: - filename (str): file name - - Returns: - int: sequence frame number - - Example: - def get_frame_from_filename(path): - ("plate.0001.exr") > 0001 - - """ - - found = re.findall(FRAME_PATTERN, filename) - - return found.pop() if found else None diff --git a/server_addon/flame/client/ayon_flame/plugins/create/create_shot_clip.py b/server_addon/flame/client/ayon_flame/plugins/create/create_shot_clip.py deleted file mode 100644 index 120c8c559d..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/create/create_shot_clip.py +++ /dev/null @@ -1,307 +0,0 @@ -from copy import deepcopy -import ayon_flame.api as opfapi - - -class CreateShotClip(opfapi.Creator): - """Publishable clip""" - - label = "Create Publishable Clip" - product_type = "clip" - icon = "film" - defaults = ["Main"] - - presets = None - - def process(self): - # Creator copy of object attributes that are modified during `process` - presets = deepcopy(self.presets) - gui_inputs = self.get_gui_inputs() - - # get key pairs from presets and match it on ui inputs - for k, v in gui_inputs.items(): - if v["type"] in ("dict", "section"): - # nested dictionary (only one level allowed - # for sections and dict) - for _k, _v in v["value"].items(): - if presets.get(_k) is not None: - gui_inputs[k][ - "value"][_k]["value"] = presets[_k] - - if presets.get(k) is not None: - gui_inputs[k]["value"] = presets[k] - - # open widget for plugins inputs - results_back = self.create_widget( - "AYON publish attributes creator", - "Define sequential rename and fill hierarchy data.", - gui_inputs - ) - - if len(self.selected) < 1: - return - - if not results_back: - print("Operation aborted") - return - - # get ui output for track name for vertical sync - v_sync_track = results_back["vSyncTrack"]["value"] - - # sort selected trackItems by - sorted_selected_segments = [] - unsorted_selected_segments = [] - for _segment in self.selected: - if _segment.parent.name.get_value() in v_sync_track: - sorted_selected_segments.append(_segment) - else: - unsorted_selected_segments.append(_segment) - - sorted_selected_segments.extend(unsorted_selected_segments) - - kwargs = { - "log": self.log, - "ui_inputs": results_back, - "avalon": self.data, - "product_type": self.data["productType"] - } - - for i, segment in enumerate(sorted_selected_segments): - kwargs["rename_index"] = i - # convert track item to timeline media pool item - opfapi.PublishableClip(segment, **kwargs).convert() - - def get_gui_inputs(self): - gui_tracks = self._get_video_track_names( - opfapi.get_current_sequence(opfapi.CTX.selection) - ) - return deepcopy({ - "renameHierarchy": { - "type": "section", - "label": "Shot Hierarchy And Rename Settings", - "target": "ui", - "order": 0, - "value": { - "hierarchy": { - "value": "{folder}/{sequence}", - "type": "QLineEdit", - "label": "Shot Parent Hierarchy", - "target": "tag", - "toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa - "order": 0}, - "useShotName": { - "value": True, - "type": "QCheckBox", - "label": "Use Shot Name", - "target": "ui", - "toolTip": "Use name form Shot name clip attribute", # noqa - "order": 1}, - "clipRename": { - "value": False, - "type": "QCheckBox", - "label": "Rename clips", - "target": "ui", - "toolTip": "Renaming selected clips on fly", # noqa - "order": 2}, - "clipName": { - "value": "{sequence}{shot}", - "type": "QLineEdit", - "label": "Clip Name Template", - "target": "ui", - "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa - "order": 3}, - "segmentIndex": { - "value": True, - "type": "QCheckBox", - "label": "Segment index", - "target": "ui", - "toolTip": "Take number from segment index", # noqa - "order": 4}, - "countFrom": { - "value": 10, - "type": "QSpinBox", - "label": "Count sequence from", - "target": "ui", - "toolTip": "Set when the sequence number stafrom", # noqa - "order": 5}, - "countSteps": { - "value": 10, - "type": "QSpinBox", - "label": "Stepping number", - "target": "ui", - "toolTip": "What number is adding every new step", # noqa - "order": 6}, - } - }, - "hierarchyData": { - "type": "dict", - "label": "Shot Template Keywords", - "target": "tag", - "order": 1, - "value": { - "folder": { - "value": "shots", - "type": "QLineEdit", - "label": "{folder}", - "target": "tag", - "toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 0}, - "episode": { - "value": "ep01", - "type": "QLineEdit", - "label": "{episode}", - "target": "tag", - "toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 1}, - "sequence": { - "value": "sq01", - "type": "QLineEdit", - "label": "{sequence}", - "target": "tag", - "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 2}, - "track": { - "value": "{_track_}", - "type": "QLineEdit", - "label": "{track}", - "target": "tag", - "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 3}, - "shot": { - "value": "sh###", - "type": "QLineEdit", - "label": "{shot}", - "target": "tag", - "toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 4} - } - }, - "verticalSync": { - "type": "section", - "label": "Vertical Synchronization Of Attributes", - "target": "ui", - "order": 2, - "value": { - "vSyncOn": { - "value": True, - "type": "QCheckBox", - "label": "Enable Vertical Sync", - "target": "ui", - "toolTip": "Switch on if you want clips above each other to share its attributes", # noqa - "order": 0}, - "vSyncTrack": { - "value": gui_tracks, # noqa - "type": "QComboBox", - "label": "Hero track", - "target": "ui", - "toolTip": "Select driving track name which should be hero for all others", # noqa - "order": 1} - } - }, - "publishSettings": { - "type": "section", - "label": "Publish Settings", - "target": "ui", - "order": 3, - "value": { - "productName": { - "value": ["[ track name ]", "main", "bg", "fg", "bg", - "animatic"], - "type": "QComboBox", - "label": "Product Name", - "target": "ui", - "toolTip": "chose product name pattern, if [ track name ] is selected, name of track layer will be used", # noqa - "order": 0}, - "productType": { - "value": ["plate", "take"], - "type": "QComboBox", - "label": "Product Type", - "target": "ui", "toolTip": "What use of this product is for", # noqa - "order": 1}, - "reviewTrack": { - "value": ["< none >"] + gui_tracks, - "type": "QComboBox", - "label": "Use Review Track", - "target": "ui", - "toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa - "order": 2}, - "audio": { - "value": False, - "type": "QCheckBox", - "label": "Include audio", - "target": "tag", - "toolTip": "Process products with corresponding audio", # noqa - "order": 3}, - "sourceResolution": { - "value": False, - "type": "QCheckBox", - "label": "Source resolution", - "target": "tag", - "toolTip": "Is resolution taken from timeline or source?", # noqa - "order": 4}, - } - }, - "frameRangeAttr": { - "type": "section", - "label": "Shot Attributes", - "target": "ui", - "order": 4, - "value": { - "workfileFrameStart": { - "value": 1001, - "type": "QSpinBox", - "label": "Workfiles Start Frame", - "target": "tag", - "toolTip": "Set workfile starting frame number", # noqa - "order": 0 - }, - "handleStart": { - "value": 0, - "type": "QSpinBox", - "label": "Handle Start", - "target": "tag", - "toolTip": "Handle at start of clip", # noqa - "order": 1 - }, - "handleEnd": { - "value": 0, - "type": "QSpinBox", - "label": "Handle End", - "target": "tag", - "toolTip": "Handle at end of clip", # noqa - "order": 2 - }, - "includeHandles": { - "value": False, - "type": "QCheckBox", - "label": "Include handles", - "target": "tag", - "toolTip": "By default handles are excluded", # noqa - "order": 3 - }, - "retimedHandles": { - "value": True, - "type": "QCheckBox", - "label": "Retimed handles", - "target": "tag", - "toolTip": "By default handles are retimed.", # noqa - "order": 4 - }, - "retimedFramerange": { - "value": True, - "type": "QCheckBox", - "label": "Retimed framerange", - "target": "tag", - "toolTip": "By default framerange is retimed.", # noqa - "order": 5 - } - } - } - }) - - def _get_video_track_names(self, sequence): - track_names = [] - for ver in sequence.versions: - for track in ver.tracks: - track_names.append(track.name.get_value()) - - return track_names diff --git a/server_addon/flame/client/ayon_flame/plugins/load/load_clip.py b/server_addon/flame/client/ayon_flame/plugins/load/load_clip.py deleted file mode 100644 index c8ec7b36c9..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/load/load_clip.py +++ /dev/null @@ -1,274 +0,0 @@ -from copy import deepcopy -import os -import flame -from pprint import pformat -import ayon_flame.api as opfapi -from ayon_core.lib import StringTemplate -from ayon_core.lib.transcoding import ( - VIDEO_EXTENSIONS, - IMAGE_EXTENSIONS -) - - -class LoadClip(opfapi.ClipLoader): - """Load a product to timeline as clip - - Place clip to timeline on its asset origin timings collected - during conforming to project - """ - - product_types = {"render2d", "source", "plate", "render", "review"} - representations = {"*"} - extensions = set( - ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) - ) - - label = "Load as clip" - order = -10 - icon = "code-fork" - color = "orange" - - # settings - reel_group_name = "OpenPype_Reels" - reel_name = "Loaded" - clip_name_template = "{folder[name]}_{product[name]}<_{output}>" - - """ Anatomy keys from version context data and dynamically added: - - {layerName} - original layer name token - - {layerUID} - original layer UID token - - {originalBasename} - original clip name taken from file - """ - layer_rename_template = "{folder[name]}_{product[name]}<_{output}>" - layer_rename_patterns = [] - - def load(self, context, name, namespace, options): - - # get flame objects - fproject = flame.project.current_project - self.fpd = fproject.current_workspace.desktop - - # load clip to timeline and get main variables - version_entity = context["version"] - version_attributes = version_entity["attrib"] - version_name = version_entity["version"] - colorspace = self.get_colorspace(context) - - # in case output is not in context replace key to representation - if not context["representation"]["context"].get("output"): - self.clip_name_template = self.clip_name_template.replace( - "output", "representation") - self.layer_rename_template = self.layer_rename_template.replace( - "output", "representation") - - formatting_data = deepcopy(context["representation"]["context"]) - clip_name = StringTemplate(self.clip_name_template).format( - formatting_data) - - # convert colorspace with ocio to flame mapping - # in imageio flame section - colorspace = self.get_native_colorspace(colorspace) - self.log.info("Loading with colorspace: `{}`".format(colorspace)) - - # create workfile path - workfile_dir = os.environ["AYON_WORKDIR"] - openclip_dir = os.path.join( - workfile_dir, clip_name - ) - openclip_path = os.path.join( - openclip_dir, clip_name + ".clip" - ) - if not os.path.exists(openclip_dir): - os.makedirs(openclip_dir) - - # prepare clip data from context ad send it to openClipLoader - path = self.filepath_from_context(context) - loading_context = { - "path": path.replace("\\", "/"), - "colorspace": colorspace, - "version": "v{:0>3}".format(version_name), - "layer_rename_template": self.layer_rename_template, - "layer_rename_patterns": self.layer_rename_patterns, - "context_data": formatting_data - } - self.log.debug(pformat( - loading_context - )) - self.log.debug(openclip_path) - - # make openpype clip file - opfapi.OpenClipSolver( - openclip_path, loading_context, logger=self.log).make() - - # prepare Reel group in actual desktop - opc = self._get_clip( - clip_name, - openclip_path - ) - - # add additional metadata from the version to imprint Avalon knob - add_keys = [ - "frameStart", "frameEnd", "source", "author", - "fps", "handleStart", "handleEnd" - ] - - # move all version data keys to tag data - data_imprint = { - key: version_attributes.get(key, str(None)) - for key in add_keys - } - - # add variables related to version context - data_imprint.update({ - "version": version_name, - "colorspace": colorspace, - "objectName": clip_name - }) - - # TODO: finish the containerisation - # opc_segment = opfapi.get_clip_segment(opc) - - # return opfapi.containerise( - # opc_segment, - # name, namespace, context, - # self.__class__.__name__, - # data_imprint) - - return opc - - def _get_clip(self, name, clip_path): - reel = self._get_reel() - # with maintained openclip as opc - matching_clip = [cl for cl in reel.clips - if cl.name.get_value() == name] - if matching_clip: - return matching_clip.pop() - else: - created_clips = flame.import_clips(str(clip_path), reel) - return created_clips.pop() - - def _get_reel(self): - - matching_rgroup = [ - rg for rg in self.fpd.reel_groups - if rg.name.get_value() == self.reel_group_name - ] - - if not matching_rgroup: - reel_group = self.fpd.create_reel_group(str(self.reel_group_name)) - for _r in reel_group.reels: - if "reel" not in _r.name.get_value().lower(): - continue - self.log.debug("Removing: {}".format(_r.name)) - flame.delete(_r) - else: - reel_group = matching_rgroup.pop() - - matching_reel = [ - re for re in reel_group.reels - if re.name.get_value() == self.reel_name - ] - - if not matching_reel: - reel_group = reel_group.create_reel(str(self.reel_name)) - else: - reel_group = matching_reel.pop() - - return reel_group - - def _get_segment_from_clip(self, clip): - # unwrapping segment from input clip - pass - - # def switch(self, container, context): - # self.update(container, context) - - # def update(self, container, context): - # """ Updating previously loaded clips - # """ - # # load clip to timeline and get main variables - # repre_entity = context['representation'] - # name = container['name'] - # namespace = container['namespace'] - # track_item = phiero.get_track_items( - # track_item_name=namespace) - # version = io.find_one({ - # "type": "version", - # "id": repre_entity["versionId"] - # }) - # version_data = version.get("data", {}) - # version_name = version.get("name", None) - # colorspace = version_data.get("colorSpace", None) - # object_name = "{}_{}".format(name, namespace) - # file = get_representation_path(repre_entity).replace("\\", "/") - # clip = track_item.source() - - # # reconnect media to new path - # clip.reconnectMedia(file) - - # # set colorspace - # if colorspace: - # clip.setSourceMediaColourTransform(colorspace) - - # # add additional metadata from the version to imprint Avalon knob - # add_keys = [ - # "frameStart", "frameEnd", "source", "author", - # "fps", "handleStart", "handleEnd" - # ] - - # # move all version data keys to tag data - # data_imprint = {} - # for key in add_keys: - # data_imprint.update({ - # key: version_data.get(key, str(None)) - # }) - - # # add variables related to version context - # data_imprint.update({ - # "representation": repre_entity["id"], - # "version": version_name, - # "colorspace": colorspace, - # "objectName": object_name - # }) - - # # update color of clip regarding the version order - # self.set_item_color(track_item, version) - - # return phiero.update_container(track_item, data_imprint) - - # def remove(self, container): - # """ Removing previously loaded clips - # """ - # # load clip to timeline and get main variables - # namespace = container['namespace'] - # track_item = phiero.get_track_items( - # track_item_name=namespace) - # track = track_item.parent() - - # # remove track item from track - # track.removeItem(track_item) - - # @classmethod - # def multiselection(cls, track_item): - # if not cls.track: - # cls.track = track_item.parent() - # cls.sequence = cls.track.parent() - - # @classmethod - # def set_item_color(cls, track_item, version): - - # clip = track_item.source() - # # define version name - # version_name = version.get("name", None) - # # get all versions in list - # versions = io.find({ - # "type": "version", - # "parent": version["parent"] - # }).distinct('name') - - # max_version = max(versions) - - # # set clip colour - # if version_name == max_version: - # clip.binItem().setColor(cls.clip_color_last) - # else: - # clip.binItem().setColor(cls.clip_color) diff --git a/server_addon/flame/client/ayon_flame/plugins/load/load_clip_batch.py b/server_addon/flame/client/ayon_flame/plugins/load/load_clip_batch.py deleted file mode 100644 index 0d7a125af7..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/load/load_clip_batch.py +++ /dev/null @@ -1,180 +0,0 @@ -from copy import deepcopy -import os -import flame -from pprint import pformat -import ayon_flame.api as opfapi -from ayon_core.lib import StringTemplate -from ayon_core.lib.transcoding import ( - VIDEO_EXTENSIONS, - IMAGE_EXTENSIONS -) - -class LoadClipBatch(opfapi.ClipLoader): - """Load a product to timeline as clip - - Place clip to timeline on its asset origin timings collected - during conforming to project - """ - - product_types = {"render2d", "source", "plate", "render", "review"} - representations = {"*"} - extensions = set( - ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) - ) - - label = "Load as clip to current batch" - order = -10 - icon = "code-fork" - color = "orange" - - # settings - reel_name = "OP_LoadedReel" - clip_name_template = "{batch}_{folder[name]}_{product[name]}<_{output}>" - - """ Anatomy keys from version context data and dynamically added: - - {layerName} - original layer name token - - {layerUID} - original layer UID token - - {originalBasename} - original clip name taken from file - """ - layer_rename_template = "{folder[name]}_{product[name]}<_{output}>" - layer_rename_patterns = [] - - def load(self, context, name, namespace, options): - - # get flame objects - self.batch = options.get("batch") or flame.batch - - # load clip to timeline and get main variables - version_entity = context["version"] - version_attributes =version_entity["attrib"] - version_name = version_entity["version"] - colorspace = self.get_colorspace(context) - - clip_name_template = self.clip_name_template - layer_rename_template = self.layer_rename_template - # in case output is not in context replace key to representation - if not context["representation"]["context"].get("output"): - clip_name_template = clip_name_template.replace( - "output", "representation") - layer_rename_template = layer_rename_template.replace( - "output", "representation") - - folder_entity = context["folder"] - product_entity = context["product"] - formatting_data = deepcopy(context["representation"]["context"]) - formatting_data["batch"] = self.batch.name.get_value() - formatting_data.update({ - "asset": folder_entity["name"], - "folder": { - "name": folder_entity["name"], - }, - "subset": product_entity["name"], - "family": product_entity["productType"], - "product": { - "name": product_entity["name"], - "type": product_entity["productType"], - } - }) - - clip_name = StringTemplate(clip_name_template).format( - formatting_data) - - # convert colorspace with ocio to flame mapping - # in imageio flame section - colorspace = self.get_native_colorspace(colorspace) - self.log.info("Loading with colorspace: `{}`".format(colorspace)) - - # create workfile path - workfile_dir = options.get("workdir") or os.environ["AYON_WORKDIR"] - openclip_dir = os.path.join( - workfile_dir, clip_name - ) - openclip_path = os.path.join( - openclip_dir, clip_name + ".clip" - ) - - if not os.path.exists(openclip_dir): - os.makedirs(openclip_dir) - - # prepare clip data from context and send it to openClipLoader - path = self.filepath_from_context(context) - loading_context = { - "path": path.replace("\\", "/"), - "colorspace": colorspace, - "version": "v{:0>3}".format(version_name), - "layer_rename_template": layer_rename_template, - "layer_rename_patterns": self.layer_rename_patterns, - "context_data": formatting_data - } - self.log.debug(pformat( - loading_context - )) - self.log.debug(openclip_path) - - # make openpype clip file - opfapi.OpenClipSolver( - openclip_path, loading_context, logger=self.log).make() - - # prepare Reel group in actual desktop - opc = self._get_clip( - clip_name, - openclip_path - ) - - # add additional metadata from the version to imprint Avalon knob - add_keys = [ - "frameStart", "frameEnd", "source", "author", - "fps", "handleStart", "handleEnd" - ] - - # move all version data keys to tag data - data_imprint = { - key: version_attributes.get(key, str(None)) - for key in add_keys - } - # add variables related to version context - data_imprint.update({ - "version": version_name, - "colorspace": colorspace, - "objectName": clip_name - }) - - # TODO: finish the containerisation - # opc_segment = opfapi.get_clip_segment(opc) - - # return opfapi.containerise( - # opc_segment, - # name, namespace, context, - # self.__class__.__name__, - # data_imprint) - - return opc - - def _get_clip(self, name, clip_path): - reel = self._get_reel() - - # with maintained openclip as opc - matching_clip = None - for cl in reel.clips: - if cl.name.get_value() != name: - continue - matching_clip = cl - - if not matching_clip: - created_clips = flame.import_clips(str(clip_path), reel) - return created_clips.pop() - - return matching_clip - - def _get_reel(self): - - matching_reel = [ - rg for rg in self.batch.reels - if rg.name.get_value() == self.reel_name - ] - - return ( - matching_reel.pop() - if matching_reel - else self.batch.create_reel(str(self.reel_name)) - ) diff --git a/server_addon/flame/client/ayon_flame/plugins/publish/collect_test_selection.py b/server_addon/flame/client/ayon_flame/plugins/publish/collect_test_selection.py deleted file mode 100644 index dac2c862e6..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/publish/collect_test_selection.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import pyblish.api -import tempfile -import ayon_flame.api as opfapi -from ayon_flame.otio import flame_export as otio_export -import opentimelineio as otio -from pprint import pformat -reload(otio_export) # noqa - - -@pyblish.api.log -class CollectTestSelection(pyblish.api.ContextPlugin): - """testing selection sharing - """ - - order = pyblish.api.CollectorOrder - label = "test selection" - hosts = ["flame"] - active = False - - def process(self, context): - self.log.info( - "Active Selection: {}".format(opfapi.CTX.selection)) - - sequence = opfapi.get_current_sequence(opfapi.CTX.selection) - - self.test_imprint_data(sequence) - self.test_otio_export(sequence) - - def test_otio_export(self, sequence): - test_dir = os.path.normpath( - tempfile.mkdtemp(prefix="test_pyblish_tmp_") - ) - export_path = os.path.normpath( - os.path.join( - test_dir, "otio_timeline_export.otio" - ) - ) - otio_timeline = otio_export.create_otio_timeline(sequence) - otio_export.write_to_file( - otio_timeline, export_path - ) - read_timeline_otio = otio.adapters.read_from_file(export_path) - - if otio_timeline != read_timeline_otio: - raise Exception("Exported timeline is different from original") - - self.log.info(pformat(otio_timeline)) - self.log.info("Otio exported to: {}".format(export_path)) - - def test_imprint_data(self, sequence): - with opfapi.maintained_segment_selection(sequence) as sel_segments: - for segment in sel_segments: - if str(segment.name)[1:-1] == "": - continue - - self.log.debug("Segment with OpenPypeData: {}".format( - segment.name)) - - opfapi.imprint(segment, { - 'asset': segment.name.get_value(), - 'productType': 'render', - 'productName': 'productMain' - }) diff --git a/server_addon/flame/client/ayon_flame/plugins/publish/collect_timeline_instances.py b/server_addon/flame/client/ayon_flame/plugins/publish/collect_timeline_instances.py deleted file mode 100644 index 7680483db1..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/publish/collect_timeline_instances.py +++ /dev/null @@ -1,419 +0,0 @@ -import re -from types import NoneType -import pyblish -import ayon_flame.api as opfapi -from ayon_flame.otio import flame_export -from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID -from ayon_core.pipeline.editorial import ( - is_overlapping_otio_ranges, - get_media_range_with_retimes -) - -# # developer reload modules -from pprint import pformat - -# constatns -NUM_PATERN = re.compile(r"([0-9\.]+)") -TXT_PATERN = re.compile(r"([a-zA-Z]+)") - - -class CollectTimelineInstances(pyblish.api.ContextPlugin): - """Collect all Timeline segment selection.""" - - order = pyblish.api.CollectorOrder - 0.09 - label = "Collect timeline Instances" - hosts = ["flame"] - - settings_category = "flame" - - audio_track_items = [] - - # settings - xml_preset_attrs_from_comments = [] - add_tasks = [] - - def process(self, context): - selected_segments = context.data["flameSelectedSegments"] - self.log.debug("__ selected_segments: {}".format(selected_segments)) - - self.otio_timeline = context.data["otioTimeline"] - self.fps = context.data["fps"] - - # process all selected - for segment in selected_segments: - # 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") not in { - AYON_INSTANCE_ID, AVALON_INSTANCE_ID - }: - continue - - self.log.debug("__ segment.name: {}".format( - segment.name - )) - - comment_attributes = self._get_comment_attributes(segment) - - self.log.debug("_ comment_attributes: {}".format( - pformat(comment_attributes))) - - clip_data = opfapi.get_segment_attributes(segment) - clip_name = clip_data["segment_name"] - self.log.debug("clip_name: {}".format(clip_name)) - - # get otio clip data - otio_data = self._get_otio_clip_instance_data(clip_data) or {} - self.log.debug("__ otio_data: {}".format(pformat(otio_data))) - - # get file path - file_path = clip_data["fpath"] - - first_frame = opfapi.get_frame_from_filename(file_path) or 0 - - head, tail = self._get_head_tail( - clip_data, - otio_data["otioClip"], - marker_data["handleStart"], - marker_data["handleEnd"] - ) - - # make sure there is not NoneType rather 0 - if isinstance(head, NoneType): - head = 0 - if isinstance(tail, NoneType): - tail = 0 - - # make sure value is absolute - if head != 0: - head = abs(head) - if tail != 0: - tail = abs(tail) - - # solve handles length - marker_data["handleStart"] = min( - marker_data["handleStart"], head) - marker_data["handleEnd"] = min( - marker_data["handleEnd"], tail) - - # Backward compatibility fix of 'entity_type' > 'folder_type' - if "parents" in marker_data: - for parent in marker_data["parents"]: - if "entity_type" in parent: - parent["folder_type"] = parent.pop("entity_type") - - workfile_start = self._set_workfile_start(marker_data) - - with_audio = bool(marker_data.pop("audio")) - - # add marker data to instance data - inst_data = dict(marker_data.items()) - - # add ocio_data to instance data - inst_data.update(otio_data) - - folder_path = marker_data["folderPath"] - folder_name = folder_path.rsplit("/")[-1] - product_name = marker_data["productName"] - - # insert product type into families - product_type = marker_data["productType"] - families = [str(f) for f in marker_data["families"]] - families.insert(0, str(product_type)) - - # form label - label = folder_name - if folder_name != clip_name: - label += " ({})".format(clip_name) - label += " {} [{}]".format(product_name, ", ".join(families)) - - inst_data.update({ - "name": "{}_{}".format(folder_name, product_name), - "label": label, - "folderPath": folder_path, - "item": segment, - "families": families, - "publish": marker_data["publish"], - "fps": self.fps, - "workfileFrameStart": workfile_start, - "sourceFirstFrame": int(first_frame), - "retimedHandles": marker_data.get("retimedHandles"), - "shotDurationFromSource": ( - not marker_data.get("retimedFramerange")), - "path": file_path, - "flameAddTasks": self.add_tasks, - "tasks": { - task["name"]: {"type": task["type"]} - for task in self.add_tasks}, - "representations": [], - "newHierarchyIntegration": True, - # Backwards compatible (Deprecated since 24/06/06) - "newAssetPublishing": True, - }) - self.log.debug("__ inst_data: {}".format(pformat(inst_data))) - - # add resolution - self._get_resolution_to_data(inst_data, context) - - # add comment attributes if any - inst_data.update(comment_attributes) - - # create instance - instance = context.create_instance(**inst_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, **inst_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 - - @staticmethod - def _set_workfile_start(data): - include_handles = data.get("includeHandles") - workfile_start = data["workfileFrameStart"] - handle_start = data["handleStart"] - - if include_handles: - workfile_start += handle_start - - return workfile_start - - def _get_comment_attributes(self, segment): - comment = segment.comment.get_value() - - # try to find attributes - attributes = { - "xml_overrides": { - "pixelRatio": 1.00} - } - # search for `:` - for split in self._split_comments(comment): - # make sure we ignore if not `:` in key - if ":" not in split: - continue - - self._get_xml_preset_attrs( - attributes, split) - - # add xml overrides resolution to instance data - xml_overrides = attributes["xml_overrides"] - if xml_overrides.get("width"): - attributes.update({ - "resolutionWidth": xml_overrides["width"], - "resolutionHeight": xml_overrides["height"], - "pixelAspect": xml_overrides["pixelRatio"] - }) - - return attributes - - def _get_xml_preset_attrs(self, attributes, split): - - # split to key and value - key, value = split.split(":") - - for attr_data in self.xml_preset_attrs_from_comments: - a_name = attr_data["name"] - a_type = attr_data["type"] - - # exclude all not related attributes - if a_name.lower() not in key.lower(): - continue - - # get pattern defined by type - pattern = TXT_PATERN - if a_type in ("number", "float"): - pattern = NUM_PATERN - - res_goup = pattern.findall(value) - - # raise if nothing is found as it is not correctly defined - if not res_goup: - raise ValueError(( - "Value for `{}` attribute is not " - "set correctly: `{}`").format(a_name, split)) - - if "string" in a_type: - _value = res_goup[0] - if "float" in a_type: - _value = float(res_goup[0]) - if "number" in a_type: - _value = int(res_goup[0]) - - attributes["xml_overrides"][a_name] = _value - - # condition for resolution in key - if "resolution" in key.lower(): - res_goup = NUM_PATERN.findall(value) - # check if axpect was also defined - # 1920x1080x1.5 - aspect = res_goup[2] if len(res_goup) > 2 else 1 - - width = int(res_goup[0]) - height = int(res_goup[1]) - pixel_ratio = float(aspect) - attributes["xml_overrides"].update({ - "width": width, - "height": height, - "pixelRatio": pixel_ratio - }) - - def _split_comments(self, comment_string): - # first split comment by comma - split_comments = [] - if "," in comment_string: - split_comments.extend(comment_string.split(",")) - elif ";" in comment_string: - split_comments.extend(comment_string.split(";")) - else: - split_comments.append(comment_string) - - return split_comments - - def _get_head_tail(self, clip_data, otio_clip, handle_start, handle_end): - # calculate head and tail with forward compatibility - head = clip_data.get("segment_head") - tail = clip_data.get("segment_tail") - self.log.debug("__ head: `{}`".format(head)) - self.log.debug("__ tail: `{}`".format(tail)) - - # HACK: it is here to serve for versions below 2021.1 - if not any([head, tail]): - retimed_attributes = get_media_range_with_retimes( - otio_clip, handle_start, handle_end) - self.log.debug( - ">> retimed_attributes: {}".format(retimed_attributes)) - - # retimed head and tail - head = int(retimed_attributes["handleStart"]) - tail = int(retimed_attributes["handleEnd"]) - - return head, tail - - 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") - - if not master_layer: - return - - if not hierarchy_data: - return - - folder_path = data["folderPath"] - folder_name = folder_path.rsplit("/")[-1] - product_name = "shotMain" - - # insert product type into families - product_type = "shot" - - # form label - label = folder_name - if folder_name != clip_name: - label += " ({}) ".format(clip_name) - label += " {}".format(product_name) - label += " [{}]".format(product_type) - - data.update({ - "name": "{}_{}".format(folder_name, product_name), - "label": label, - "productName": product_name, - "folderPath": folder_path, - "productType": product_type, - "family": product_type, - "families": [product_type] - }) - - 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, 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 - - Returns: - dict: otio clip object - - """ - segment = clip_data["PySegment"] - 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(): - 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 is_overlapping_otio_ranges( - parent_range, timeline_range, strict=True): - - # 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) diff --git a/server_addon/flame/client/ayon_flame/plugins/publish/collect_timeline_otio.py b/server_addon/flame/client/ayon_flame/plugins/publish/collect_timeline_otio.py deleted file mode 100644 index 139ac5b875..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/publish/collect_timeline_otio.py +++ /dev/null @@ -1,67 +0,0 @@ -import pyblish.api - -import ayon_flame.api as opfapi -from ayon_flame.otio import flame_export -from ayon_core.pipeline.create import get_product_name - - -class CollecTimelineOTIO(pyblish.api.ContextPlugin): - """Inject the current working context into publish context""" - - label = "Collect Timeline OTIO" - order = pyblish.api.CollectorOrder - 0.099 - - def process(self, context): - # plugin defined - product_type = "workfile" - variant = "otioTimeline" - - # main - folder_entity = context.data["folderEntity"] - project = opfapi.get_current_project() - sequence = opfapi.get_current_sequence(opfapi.CTX.selection) - - # create product name - task_entity = context.data["taskEntity"] - task_name = task_type = None - if task_entity: - task_name = task_entity["name"] - task_type = task_entity["taskType"] - product_name = get_product_name( - context.data["projectName"], - task_name, - task_type, - context.data["hostName"], - product_type, - variant, - project_settings=context.data["project_settings"] - ) - - # adding otio timeline to context - with opfapi.maintained_segment_selection(sequence) as selected_seg: - otio_timeline = flame_export.create_otio_timeline(sequence) - - instance_data = { - "name": product_name, - "folderPath": folder_entity["path"], - "productName": product_name, - "productType": product_type, - "family": product_type, - "families": [product_type] - } - - # 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({ - "flameProject": project, - "flameSequence": sequence, - "otioTimeline": otio_timeline, - "currentFile": "Flame/{}/{}".format( - project.name, sequence.name - ), - "flameSelectedSegments": selected_seg, - "fps": float(str(sequence.frame_rate)[:-4]) - }) diff --git a/server_addon/flame/client/ayon_flame/plugins/publish/extract_otio_file.py b/server_addon/flame/client/ayon_flame/plugins/publish/extract_otio_file.py deleted file mode 100644 index 41ae981cba..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/publish/extract_otio_file.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -import pyblish.api -import opentimelineio as otio -from ayon_core.pipeline import publish - - -class ExtractOTIOFile(publish.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)) diff --git a/server_addon/flame/client/ayon_flame/plugins/publish/extract_subset_resources.py b/server_addon/flame/client/ayon_flame/plugins/publish/extract_subset_resources.py deleted file mode 100644 index 66c6181ffb..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/publish/extract_subset_resources.py +++ /dev/null @@ -1,560 +0,0 @@ -import os -import re -from copy import deepcopy - -import pyblish.api - -from ayon_core.pipeline import publish -from ayon_flame import api as opfapi -from ayon_flame.api import MediaInfoFile -from ayon_core.pipeline.editorial import ( - get_media_range_with_retimes -) - -import flame - - -class ExtractProductResources(publish.Extractor): - """ - Extractor for transcoding files from Flame clip - """ - - label = "Extract product resources" - order = pyblish.api.ExtractorOrder - families = ["clip"] - hosts = ["flame"] - - settings_category = "flame" - - # plugin defaults - keep_original_representation = False - - default_presets = { - "thumbnail": { - "active": True, - "ext": "jpg", - "xml_preset_file": "Jpeg (8-bit).xml", - "xml_preset_dir": "", - "export_type": "File Sequence", - "parsed_comment_attrs": False, - "colorspace_out": "Output - sRGB", - "representation_add_range": False, - "representation_tags": ["thumbnail"], - "path_regex": ".*" - } - } - - # hide publisher during exporting - hide_ui_on_process = True - - # settings - export_presets_mapping = [] - - def process(self, instance): - if not self.keep_original_representation: - # remove previeous representation if not needed - instance.data["representations"] = [] - - # flame objects - segment = instance.data["item"] - folder_path = instance.data["folderPath"] - segment_name = segment.name.get_value() - clip_path = instance.data["path"] - sequence_clip = instance.context.data["flameSequence"] - - # 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"] - - self.log.debug("_ frame_start: {}".format(frame_start)) - self.log.debug("_ source_first_frame: {}".format(source_first_frame)) - - # get timeline in/out of segment - clip_in = instance.data["clipIn"] - clip_out = instance.data["clipOut"] - - # get retimed attributres - retimed_data = self._get_retimed_attributes(instance) - - # get individual keys - retimed_handle_start = retimed_data["handle_start"] - retimed_handle_end = retimed_data["handle_end"] - retimed_source_duration = retimed_data["source_duration"] - retimed_speed = retimed_data["speed"] - - # get handles value - take only the max from both - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - handles = max(handle_start, handle_end) - include_handles = instance.data.get("includeHandles") - retimed_handles = instance.data.get("retimedHandles") - - # get media source range with handles - source_start_handles = instance.data["sourceStartH"] - source_end_handles = instance.data["sourceEndH"] - - # retime if needed - if retimed_speed != 1.0: - if retimed_handles: - # handles are retimed - source_start_handles = ( - instance.data["sourceStart"] - retimed_handle_start) - source_end_handles = ( - source_start_handles - + (retimed_source_duration - 1) - + retimed_handle_start - + retimed_handle_end - ) - - else: - # handles are not retimed - source_end_handles = ( - source_start_handles - + (retimed_source_duration - 1) - + handle_start - + handle_end - ) - - # get frame range with handles for representation range - frame_start_handle = frame_start - handle_start - repre_frame_start = frame_start_handle - if include_handles: - if retimed_speed == 1.0 or not retimed_handles: - frame_start_handle = frame_start - else: - frame_start_handle = ( - frame_start - handle_start) + retimed_handle_start - - self.log.debug("_ frame_start_handle: {}".format( - frame_start_handle)) - self.log.debug("_ repre_frame_start: {}".format( - repre_frame_start)) - - # calculate duration with handles - source_duration_handles = ( - source_end_handles - source_start_handles) + 1 - - self.log.debug("_ source_duration_handles: {}".format( - source_duration_handles)) - - # create staging dir path - staging_dir = self.staging_dir(instance) - - # append staging dir for later cleanup - instance.context.data["cleanupFullPaths"].append(staging_dir) - - export_presets_mapping = {} - for preset_mapping in deepcopy(self.export_presets_mapping): - name = preset_mapping.pop("name") - export_presets_mapping[name] = preset_mapping - - # add default preset type for thumbnail and reviewable video - # update them with settings and override in case the same - # are found in there - _preset_keys = [k.split('_')[0] for k in export_presets_mapping] - export_presets = { - k: v - for k, v in deepcopy(self.default_presets).items() - if k not in _preset_keys - } - export_presets.update(export_presets_mapping) - - if not instance.data.get("versionData"): - instance.data["versionData"] = {} - - # set versiondata if any retime - version_data = retimed_data.get("version_data") - self.log.debug("_ version_data: {}".format(version_data)) - - if version_data: - instance.data["versionData"].update(version_data) - - # version data start frame - version_frame_start = frame_start - if include_handles: - version_frame_start = frame_start_handle - if retimed_speed != 1.0: - if retimed_handles: - instance.data["versionData"].update({ - "frameStart": version_frame_start, - "frameEnd": ( - (version_frame_start + source_duration_handles - 1) - - (retimed_handle_start + retimed_handle_end) - ) - }) - else: - instance.data["versionData"].update({ - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStart": version_frame_start, - "frameEnd": ( - (version_frame_start + source_duration_handles - 1) - - (handle_start + handle_end) - ) - }) - self.log.debug("_ version_data: {}".format( - instance.data["versionData"] - )) - - # loop all preset names and - for unique_name, preset_config in export_presets.items(): - modify_xml_data = {} - - if self._should_skip(preset_config, clip_path, unique_name): - continue - - # get all presets attributes - extension = preset_config["ext"] - 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"] - parsed_comment_attrs = preset_config["parsed_comment_attrs"] - color_out = preset_config["colorspace_out"] - - self.log.info( - "Processing `{}` as `{}` to `{}` type...".format( - preset_file, export_type, extension - ) - ) - - exporting_clip = None - name_patern_xml = "_{}.".format( - unique_name) - - if export_type == "Sequence Publish": - # change export clip to sequence - exporting_clip = flame.duplicate(sequence_clip) - - # only keep visible layer where instance segment is child - self.hide_others( - exporting_clip, segment_name, s_track_name) - - # change name pattern - name_patern_xml = ( - "__{}.").format( - unique_name) - - # only for h264 with baked retime - in_mark = clip_in - out_mark = clip_out + 1 - modify_xml_data.update({ - "exportHandles": True, - "nbHandles": handles - }) - else: - in_mark = (source_start_handles - source_first_frame) + 1 - out_mark = in_mark + source_duration_handles - exporting_clip = self.import_clip(clip_path) - exporting_clip.name.set_value("{}_{}".format( - folder_path, segment_name)) - - # add xml tags modifications - modify_xml_data.update({ - # enum position low start from 0 - "frameIndex": 0, - "startFrame": repre_frame_start, - "namePattern": name_patern_xml - }) - - if parsed_comment_attrs: - # add any xml overrides collected form segment.comment - modify_xml_data.update(instance.data["xml_overrides"]) - - self.log.debug("_ in_mark: {}".format(in_mark)) - self.log.debug("_ out_mark: {}".format(out_mark)) - - export_kwargs = {} - # validate xml preset file is filled - if preset_file == "": - raise ValueError( - ("Check Settings for {} preset: " - "`XML preset file` is not filled").format( - unique_name) - ) - - # resolve xml preset dir if not filled - if preset_dir == "": - preset_dir = opfapi.get_preset_path_by_xml_name( - preset_file) - - if not preset_dir: - raise ValueError( - ("Check Settings for {} preset: " - "`XML preset file` {} is not found").format( - unique_name, preset_file) - ) - - # create preset path - preset_orig_xml_path = str(os.path.join( - preset_dir, preset_file - )) - - # define kwargs based on preset type - if "thumbnail" in unique_name: - modify_xml_data.update({ - "video/posterFrame": True, - "video/useFrameAsPoster": 1, - "namePattern": "__thumbnail" - }) - thumb_frame_number = int(in_mark + ( - (out_mark - in_mark + 1) / 2)) - - self.log.debug("__ thumb_frame_number: {}".format( - thumb_frame_number - )) - - export_kwargs["thumb_frame_number"] = thumb_frame_number - else: - export_kwargs.update({ - "in_mark": in_mark, - "out_mark": out_mark - }) - - preset_path = opfapi.modify_preset_file( - preset_orig_xml_path, staging_dir, modify_xml_data) - - # get and make export dir paths - export_dir_path = str(os.path.join( - staging_dir, unique_name - )) - os.makedirs(export_dir_path) - - # export - opfapi.export_clip( - export_dir_path, exporting_clip, preset_path, **export_kwargs) - - repr_name = unique_name - # make sure only first segment is used if underscore in name - # HACK: `ftrackreview_withLUT` will result only in `ftrackreview` - if ( - "thumbnail" in unique_name - or "ftrackreview" in unique_name - ): - repr_name = unique_name.split("_")[0] - - # create representation data - representation_data = { - "name": repr_name, - "outputName": repr_name, - "ext": extension, - "stagingDir": export_dir_path, - "tags": repre_tags, - "data": { - "colorspace": color_out - }, - "load_to_batch_group": preset_config.get( - "load_to_batch_group"), - "batch_group_loader_name": preset_config.get( - "batch_group_loader_name") or None - } - - # collect all available content of export dir - files = os.listdir(export_dir_path) - - # make sure no nested folders inside - n_stage_dir, n_files = self._unfolds_nested_folders( - export_dir_path, files, extension) - - # fix representation in case of nested folders - if n_stage_dir: - representation_data["stagingDir"] = n_stage_dir - files = n_files - - # add files to representation but add - # imagesequence as list - if ( - # 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 repr_name == "thumbnail" - ): - representation_data["files"] = files.pop() - else: - representation_data["files"] = files - - # add frame range - if preset_config["representation_add_range"]: - representation_data.update({ - "frameStart": repre_frame_start, - "frameEnd": ( - repre_frame_start + source_duration_handles) - 1, - "fps": instance.data["fps"] - }) - - instance.data["representations"].append(representation_data) - - # add review family if found in tags - if "review" in repre_tags: - instance.data["families"].append("review") - - self.log.info("Added representation: {}".format( - representation_data)) - - if export_type == "Sequence Publish": - # at the end remove the duplicated clip - flame.delete(exporting_clip) - - def _get_retimed_attributes(self, instance): - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - - # get basic variables - otio_clip = instance.data["otioClip"] - - # get available range trimmed with processed retimes - retimed_attributes = get_media_range_with_retimes( - otio_clip, handle_start, handle_end) - self.log.debug( - ">> retimed_attributes: {}".format(retimed_attributes)) - - r_media_in = int(retimed_attributes["mediaIn"]) - r_media_out = int(retimed_attributes["mediaOut"]) - version_data = retimed_attributes.get("versionData") - - return { - "version_data": version_data, - "handle_start": int(retimed_attributes["handleStart"]), - "handle_end": int(retimed_attributes["handleEnd"]), - "source_duration": ( - (r_media_out - r_media_in) + 1 - ), - "speed": float(retimed_attributes["speed"]) - } - - def _should_skip(self, preset_config, clip_path, unique_name): - # get activating attributes - activated_preset = preset_config["active"] - filter_path_regex = preset_config.get("filter_path_regex") - - self.log.info( - "Preset `{}` is active `{}` with filter `{}`".format( - unique_name, activated_preset, filter_path_regex - ) - ) - - # skip if not activated presete - if not activated_preset: - return True - - # exclude by regex filter if any - if ( - filter_path_regex - and not re.search(filter_path_regex, clip_path) - ): - return True - - def _unfolds_nested_folders(self, stage_dir, files_list, ext): - """Unfolds nested folders - - Args: - stage_dir (str): path string with directory - files_list (list): list of file names - ext (str): extension (jpg)[without dot] - - Raises: - IOError: in case no files were collected form any directory - - Returns: - str, list: new staging dir path, new list of file names - or - None, None: In case single file in `files_list` - """ - # exclude single files which are having extension - # the same as input ext attr - if ( - # only one file in list - len(files_list) == 1 - # file is having extension as input - and ext in os.path.splitext(files_list[0])[-1] - ): - return None, None - elif ( - # more then one file in list - len(files_list) >= 1 - # extension is correct - and ext in os.path.splitext(files_list[0])[-1] - # test file exists - and os.path.exists( - os.path.join(stage_dir, files_list[0]) - ) - ): - return None, None - - new_stage_dir = None - new_files_list = [] - for file in files_list: - search_path = os.path.join(stage_dir, file) - if not os.path.isdir(search_path): - continue - for root, _dirs, files in os.walk(search_path): - for _file in files: - _fn, _ext = os.path.splitext(_file) - if ext.lower() != _ext[1:].lower(): - continue - new_files_list.append(_file) - if not new_stage_dir: - new_stage_dir = root - - if not new_stage_dir: - raise AssertionError( - "Files in `{}` are not correct! Check `{}`".format( - files_list, stage_dir) - ) - - return new_stage_dir, new_files_list - - def hide_others(self, sequence_clip, segment_name, track_name): - """Helper method used only if sequence clip is used - - Args: - sequence_clip (flame.Clip): sequence clip - segment_name (str): segment name - 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.get_value(): - continue - - # hide tracks which are not parent track - if track.name.get_value() != track_name: - track.hidden = True - continue - - # hidde all other segments - for segment in track.segments: - if segment.name.get_value() != segment_name: - segment.hidden = True - - def import_clip(self, path): - """ - Import clip from path - """ - dir_path = os.path.dirname(path) - media_info = MediaInfoFile(path, logger=self.log) - file_pattern = media_info.file_pattern - self.log.debug("__ file_pattern: {}".format(file_pattern)) - - # rejoin the pattern to dir path - new_path = os.path.join(dir_path, file_pattern) - - clips = flame.import_clips(new_path) - self.log.info("Clips [{}] imported from `{}`".format(clips, path)) - - if not clips: - self.log.warning("Path `{}` is not having any clips".format(path)) - return None - elif len(clips) > 1: - self.log.warning( - "Path `{}` is containing more that one clip".format(path) - ) - return clips[0] diff --git a/server_addon/flame/client/ayon_flame/plugins/publish/integrate_batch_group.py b/server_addon/flame/client/ayon_flame/plugins/publish/integrate_batch_group.py deleted file mode 100644 index f77c9e9116..0000000000 --- a/server_addon/flame/client/ayon_flame/plugins/publish/integrate_batch_group.py +++ /dev/null @@ -1,339 +0,0 @@ -import os -import copy -from collections import OrderedDict -from pprint import pformat -import pyblish -import ayon_flame.api as opfapi -import ayon_core.pipeline as op_pipeline -from ayon_core.pipeline.workfile import get_workdir - - -class IntegrateBatchGroup(pyblish.api.InstancePlugin): - """Integrate published shot to batch group""" - - order = pyblish.api.IntegratorOrder + 0.45 - label = "Integrate Batch Groups" - hosts = ["flame"] - families = ["clip"] - - settings_category = "flame" - - # settings - default_loader = "LoadClip" - - def process(self, instance): - add_tasks = instance.data["flameAddTasks"] - - # iterate all tasks from settings - for task_data in add_tasks: - # exclude batch group - if not task_data["create_batch_group"]: - continue - - # create or get already created batch group - bgroup = self._get_batch_group(instance, task_data) - - # add batch group content - all_batch_nodes = self._add_nodes_to_batch_with_links( - instance, task_data, bgroup) - - for name, node in all_batch_nodes.items(): - self.log.debug("name: {}, dir: {}".format( - name, dir(node) - )) - self.log.debug("__ node.attributes: {}".format( - node.attributes - )) - - # load plate to batch group - self.log.info("Loading product `{}` into batch `{}`".format( - instance.data["productName"], bgroup.name.get_value() - )) - self._load_clip_to_context(instance, bgroup) - - def _add_nodes_to_batch_with_links(self, instance, task_data, batch_group): - # get write file node properties > OrederDict because order does matter - write_pref_data = self._get_write_prefs(instance, task_data) - - batch_nodes = [ - { - "type": "comp", - "properties": {}, - "id": "comp_node01" - }, - { - "type": "Write File", - "properties": write_pref_data, - "id": "write_file_node01" - } - ] - batch_links = [ - { - "from_node": { - "id": "comp_node01", - "connector": "Result" - }, - "to_node": { - "id": "write_file_node01", - "connector": "Front" - } - } - ] - - # add nodes into batch group - return opfapi.create_batch_group_conent( - batch_nodes, batch_links, batch_group) - - def _load_clip_to_context(self, instance, bgroup): - # get all loaders for host - loaders_by_name = { - loader.__name__: loader - for loader in op_pipeline.discover_loader_plugins() - } - - # get all published representations - published_representations = instance.data["published_representations"] - repres_db_id_by_name = { - repre_info["representation"]["name"]: repre_id - for repre_id, repre_info in published_representations.items() - } - - # get all loadable representations - repres_by_name = { - repre["name"]: repre for repre in instance.data["representations"] - } - - # get repre_id for the loadable representations - loader_name_by_repre_id = { - repres_db_id_by_name[repr_name]: { - "loader": repr_data["batch_group_loader_name"], - # add repre data for exception logging - "_repre_data": repr_data - } - for repr_name, repr_data in repres_by_name.items() - if repr_data.get("load_to_batch_group") - } - - self.log.debug("__ loader_name_by_repre_id: {}".format(pformat( - loader_name_by_repre_id))) - - # get representation context from the repre_id - repre_contexts = op_pipeline.load.get_repres_contexts( - loader_name_by_repre_id.keys()) - - self.log.debug("__ repre_contexts: {}".format(pformat( - repre_contexts))) - - # loop all returned repres from repre_context dict - for repre_id, repre_context in repre_contexts.items(): - self.log.debug("__ repre_id: {}".format(repre_id)) - # get loader name by representation id - loader_name = ( - loader_name_by_repre_id[repre_id]["loader"] - # if nothing was added to settings fallback to default - or self.default_loader - ) - - # get loader plugin - loader_plugin = loaders_by_name.get(loader_name) - if loader_plugin: - # load to flame by representation context - try: - op_pipeline.load.load_with_repre_context( - loader_plugin, repre_context, **{ - "data": { - "workdir": self.task_workdir, - "batch": bgroup - } - }) - except op_pipeline.load.IncompatibleLoaderError as msg: - self.log.error( - "Check allowed representations for Loader `{}` " - "in settings > error: {}".format( - loader_plugin.__name__, msg)) - self.log.error( - "Representaton context >>{}<< is not compatible " - "with loader `{}`".format( - pformat(repre_context), loader_plugin.__name__ - ) - ) - else: - self.log.warning( - "Something got wrong and there is not Loader found for " - "following data: {}".format( - pformat(loader_name_by_repre_id)) - ) - - def _get_batch_group(self, instance, task_data): - frame_start = instance.data["frameStart"] - frame_end = instance.data["frameEnd"] - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - frame_duration = (frame_end - frame_start) + 1 - folder_path = instance.data["folderPath"] - - task_name = task_data["name"] - batchgroup_name = "{}_{}".format(folder_path, task_name) - - batch_data = { - "shematic_reels": [ - "OP_LoadedReel" - ], - "handleStart": handle_start, - "handleEnd": handle_end - } - self.log.debug( - "__ batch_data: {}".format(pformat(batch_data))) - - # check if the batch group already exists - bgroup = opfapi.get_batch_group_from_desktop(batchgroup_name) - - if not bgroup: - self.log.info( - "Creating new batch group: {}".format(batchgroup_name)) - # create batch with utils - bgroup = opfapi.create_batch_group( - batchgroup_name, - frame_start, - frame_duration, - **batch_data - ) - - else: - self.log.info( - "Updating batch group: {}".format(batchgroup_name)) - # update already created batch group - bgroup = opfapi.create_batch_group( - batchgroup_name, - frame_start, - frame_duration, - update_batch_group=bgroup, - **batch_data - ) - - return bgroup - - def _get_anamoty_data_with_current_task(self, instance, task_data): - anatomy_data = copy.deepcopy(instance.data["anatomyData"]) - task_name = task_data["name"] - task_type = task_data["type"] - anatomy_obj = instance.context.data["anatomy"] - - # update task data in anatomy data - project_task_types = anatomy_obj["tasks"] - task_code = project_task_types.get(task_type, {}).get("shortName") - anatomy_data.update({ - "task": { - "name": task_name, - "type": task_type, - "short": task_code - } - }) - return anatomy_data - - def _get_write_prefs(self, instance, task_data): - # update task in anatomy data - anatomy_data = self._get_anamoty_data_with_current_task( - instance, task_data) - - self.task_workdir = self._get_shot_task_dir_path( - instance, task_data) - self.log.debug("__ task_workdir: {}".format( - self.task_workdir)) - - # TODO: this might be done with template in settings - render_dir_path = os.path.join( - self.task_workdir, "render", "flame") - - if not os.path.exists(render_dir_path): - os.makedirs(render_dir_path, mode=0o777) - - # TODO: add most of these to `imageio/flame/batch/write_node` - name = "{project[code]}_{folder[name]}_{task[name]}".format( - **anatomy_data - ) - - # The path attribute where the rendered clip is exported - # /path/to/file.[0001-0010].exr - media_path = render_dir_path - # name of file represented by tokens - media_path_pattern = ( - "_v/_v.") - # The Create Open Clip attribute of the Write File node. \ - # Determines if an Open Clip is created by the Write File node. - create_clip = True - # The Include Setup attribute of the Write File node. - # Determines if a Batch Setup file is created by the Write File node. - include_setup = True - # The path attribute where the Open Clip file is exported by - # the Write File node. - create_clip_path = "" - # The path attribute where the Batch setup file - # is exported by the Write File node. - include_setup_path = "./_v" - # The file type for the files written by the Write File node. - # Setting this attribute also overwrites format_extension, - # bit_depth and compress_mode to match the defaults for - # this file type. - file_type = "OpenEXR" - # The file extension for the files written by the Write File node. - # This attribute resets to match file_type whenever file_type - # is set. If you require a specific extension, you must - # set format_extension after setting file_type. - format_extension = "exr" - # The bit depth for the files written by the Write File node. - # This attribute resets to match file_type whenever file_type is set. - bit_depth = "16" - # The compressing attribute for the files exported by the Write - # File node. Only relevant when file_type in 'OpenEXR', 'Sgi', 'Tiff' - compress = True - # The compression format attribute for the specific File Types - # export by the Write File node. You must set compress_mode - # after setting file_type. - compress_mode = "DWAB" - # The frame index mode attribute of the Write File node. - # Value range: `Use Timecode` or `Use Start Frame` - frame_index_mode = "Use Start Frame" - frame_padding = 6 - # The versioning mode of the Open Clip exported by the Write File node. - # Only available if create_clip = True. - version_mode = "Follow Iteration" - version_name = "v" - version_padding = 3 - - # need to make sure the order of keys is correct - return OrderedDict(( - ("name", name), - ("media_path", media_path), - ("media_path_pattern", media_path_pattern), - ("create_clip", create_clip), - ("include_setup", include_setup), - ("create_clip_path", create_clip_path), - ("include_setup_path", include_setup_path), - ("file_type", file_type), - ("format_extension", format_extension), - ("bit_depth", bit_depth), - ("compress", compress), - ("compress_mode", compress_mode), - ("frame_index_mode", frame_index_mode), - ("frame_padding", frame_padding), - ("version_mode", version_mode), - ("version_name", version_name), - ("version_padding", version_padding) - )) - - def _get_shot_task_dir_path(self, instance, task_data): - project_entity = instance.data["projectEntity"] - folder_entity = instance.data["folderEntity"] - task_entity = instance.data["taskEntity"] - anatomy = instance.context.data["anatomy"] - project_settings = instance.context.data["project_settings"] - - return get_workdir( - project_entity, - folder_entity, - task_entity, - "flame", - anatomy=anatomy, - project_settings=project_settings - ) diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/export_preset/openpype_seg_thumbnails_jpg.xml b/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/export_preset/openpype_seg_thumbnails_jpg.xml deleted file mode 100644 index 44a7bd9770..0000000000 --- a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/export_preset/openpype_seg_thumbnails_jpg.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - sequence - Creates a 8-bit Jpeg file per segment. - - NONE - - <name> - True - True - - image - FX - NoChange - False - 10 - - True - False - - audio - FX - FlattenTracks - True - 10 - - - - - 4 - 1 - 2 - - diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/export_preset/openpype_seg_video_h264.xml b/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/export_preset/openpype_seg_video_h264.xml deleted file mode 100644 index 1d2c5a28bb..0000000000 --- a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/export_preset/openpype_seg_video_h264.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - sequence - Create MOV H264 files per segment with thumbnail - - NONE - - <name> - True - True - - movie - FX - FlattenTracks - True - 5 - - True - False - - audio - Original - NoChange - True - 5 - - - - QuickTime - <shot name> - 0 - PCS_709 - None - Autodesk - Flame - 2021 - - - - 4 - 1 - 2 - - \ No newline at end of file diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/__init__.py b/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/app_utils.py b/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/app_utils.py deleted file mode 100644 index e639c3f482..0000000000 --- a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/app_utils.py +++ /dev/null @@ -1,162 +0,0 @@ -import os -import io -import ConfigParser as CP -from xml.etree import ElementTree as ET -from contextlib import contextmanager - -PLUGIN_DIR = os.path.dirname(os.path.dirname(__file__)) -EXPORT_PRESETS_DIR = os.path.join(PLUGIN_DIR, "export_preset") - -CONFIG_DIR = os.path.join(os.path.expanduser( - "~/.openpype"), "openpype_babypublisher") - - -@contextmanager -def make_temp_dir(): - import tempfile - - try: - dirpath = tempfile.mkdtemp() - - yield dirpath - - except IOError as _error: - raise IOError("Not able to create temp dir file: {}".format(_error)) - - finally: - pass - - -@contextmanager -def get_config(section=None): - cfg_file_path = os.path.join(CONFIG_DIR, "settings.ini") - - # create config dir - if not os.path.exists(CONFIG_DIR): - print("making dirs at: `{}`".format(CONFIG_DIR)) - os.makedirs(CONFIG_DIR, mode=0o777) - - # write default data to settings.ini - if not os.path.exists(cfg_file_path): - default_cfg = cfg_default() - config = CP.RawConfigParser() - config.readfp(io.BytesIO(default_cfg)) - with open(cfg_file_path, 'wb') as cfg_file: - config.write(cfg_file) - - try: - config = CP.RawConfigParser() - config.read(cfg_file_path) - if section: - _cfg_data = { - k: v - for s in config.sections() - for k, v in config.items(s) - if s == section - } - else: - _cfg_data = {s: dict(config.items(s)) for s in config.sections()} - - yield _cfg_data - - except IOError as _error: - raise IOError('Not able to read settings.ini file: {}'.format(_error)) - - finally: - pass - - -def set_config(cfg_data, section=None): - cfg_file_path = os.path.join(CONFIG_DIR, "settings.ini") - - config = CP.RawConfigParser() - config.read(cfg_file_path) - - try: - if not section: - for section in cfg_data: - for key, value in cfg_data[section].items(): - config.set(section, key, value) - else: - for key, value in cfg_data.items(): - config.set(section, key, value) - - with open(cfg_file_path, 'wb') as cfg_file: - config.write(cfg_file) - - except IOError as _error: - raise IOError('Not able to write settings.ini file: {}'.format(_error)) - - -def cfg_default(): - return """ -[main] -workfile_start_frame = 1001 -shot_handles = 0 -shot_name_template = {sequence}_{shot} -hierarchy_template = shots[Folder]/{sequence}[Sequence] -create_task_type = Compositing -""" - - -def configure_preset(file_path, data): - split_fp = os.path.splitext(file_path) - new_file_path = split_fp[0] + "_tmp" + split_fp[-1] - with open(file_path, "r") as datafile: - tree = ET.parse(datafile) - for key, value in data.items(): - for element in tree.findall(".//{}".format(key)): - print(element) - element.text = str(value) - tree.write(new_file_path) - - return new_file_path - - -def export_thumbnail(sequence, tempdir_path, data): - import flame - export_preset = os.path.join( - EXPORT_PRESETS_DIR, - "openpype_seg_thumbnails_jpg.xml" - ) - new_path = configure_preset(export_preset, data) - poster_frame_exporter = flame.PyExporter() - poster_frame_exporter.foreground = True - poster_frame_exporter.export(sequence, new_path, tempdir_path) - - -def export_video(sequence, tempdir_path, data): - import flame - export_preset = os.path.join( - EXPORT_PRESETS_DIR, - "openpype_seg_video_h264.xml" - ) - new_path = configure_preset(export_preset, data) - poster_frame_exporter = flame.PyExporter() - poster_frame_exporter.foreground = True - poster_frame_exporter.export(sequence, new_path, tempdir_path) - - -def timecode_to_frames(timecode, framerate): - def _seconds(value): - if isinstance(value, str): - _zip_ft = zip((3600, 60, 1, 1 / framerate), value.split(':')) - return sum(f * float(t) for f, t in _zip_ft) - elif isinstance(value, (int, float)): - return value / framerate - return 0 - - def _frames(seconds): - return seconds * framerate - - def tc_to_frames(_timecode, start=None): - return _frames(_seconds(_timecode) - _seconds(start)) - - if '+' in timecode: - timecode = timecode.replace('+', ':') - elif '#' in timecode: - timecode = timecode.replace('#', ':') - - frames = int(round(tc_to_frames(timecode, start='00:00:00:00'))) - - return frames diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/ftrack_lib.py b/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/ftrack_lib.py deleted file mode 100644 index a66980493e..0000000000 --- a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/ftrack_lib.py +++ /dev/null @@ -1,459 +0,0 @@ -import os -import sys -import six -import re -import json - -import app_utils - -# Fill following constants or set them via environment variable -FTRACK_MODULE_PATH = None -FTRACK_API_KEY = None -FTRACK_API_USER = None -FTRACK_SERVER = None - - -def import_ftrack_api(): - try: - import ftrack_api - return ftrack_api - except ImportError: - import sys - ftrk_m_p = FTRACK_MODULE_PATH or os.getenv("FTRACK_MODULE_PATH") - sys.path.append(ftrk_m_p) - import ftrack_api - return ftrack_api - - -def get_ftrack_session(): - import os - ftrack_api = import_ftrack_api() - - # fill your own credentials - url = FTRACK_SERVER or os.getenv("FTRACK_SERVER") or "" - user = FTRACK_API_USER or os.getenv("FTRACK_API_USER") or "" - api = FTRACK_API_KEY or os.getenv("FTRACK_API_KEY") or "" - - first_validation = True - if not user: - print('- Ftrack Username is not set') - first_validation = False - if not api: - print('- Ftrack API key is not set') - first_validation = False - if not first_validation: - return False - - try: - return ftrack_api.Session( - server_url=url, - api_user=user, - api_key=api - ) - except Exception as _e: - print("Can't log into Ftrack with used credentials: {}".format(_e)) - ftrack_cred = { - 'Ftrack server': str(url), - 'Username': str(user), - 'API key': str(api), - } - - item_lens = [len(key) + 1 for key in ftrack_cred] - justify_len = max(*item_lens) - for key, value in ftrack_cred.items(): - print('{} {}'.format((key + ':').ljust(justify_len, ' '), value)) - return False - - -def get_project_task_types(project_entity): - tasks = {} - proj_template = project_entity['project_schema'] - temp_task_types = proj_template['_task_type_schema']['types'] - - for type in temp_task_types: - if type['name'] not in tasks: - tasks[type['name']] = type - - return tasks - - -class FtrackComponentCreator: - default_location = "ftrack.server" - ftrack_locations = {} - thumbnails = [] - videos = [] - temp_dir = None - - def __init__(self, session): - self.session = session - self._get_ftrack_location() - - def generate_temp_data(self, selection, change_preset_data): - with app_utils.make_temp_dir() as tempdir_path: - for seq in selection: - app_utils.export_thumbnail( - seq, tempdir_path, change_preset_data) - app_utils.export_video(seq, tempdir_path, change_preset_data) - - return tempdir_path - - def collect_generated_data(self, tempdir_path): - temp_files = os.listdir(tempdir_path) - self.thumbnails = [f for f in temp_files if "jpg" in f] - self.videos = [f for f in temp_files if "mov" in f] - self.temp_dir = tempdir_path - - def get_thumb_path(self, shot_name): - # get component files - thumb_f = next((f for f in self.thumbnails if shot_name in f), None) - return os.path.join(self.temp_dir, thumb_f) - - def get_video_path(self, shot_name): - # get component files - video_f = next((f for f in self.videos if shot_name in f), None) - return os.path.join(self.temp_dir, video_f) - - def close(self): - self.ftrack_locations = {} - self.session = None - - def create_comonent(self, shot_entity, data, assetversion_entity=None): - self.shot_entity = shot_entity - location = self._get_ftrack_location() - - file_path = data["file_path"] - - # get extension - file = os.path.basename(file_path) - _n, ext = os.path.splitext(file) - - name = "ftrackreview-mp4" if "mov" in ext else "thumbnail" - - component_data = { - "name": name, - "file_path": file_path, - "file_type": ext, - "location": location - } - - if name == "ftrackreview-mp4": - duration = data["duration"] - handles = data["handles"] - fps = data["fps"] - component_data["metadata"] = { - 'ftr_meta': json.dumps({ - 'frameIn': int(0), - 'frameOut': int(duration + (handles * 2)), - 'frameRate': float(fps) - }) - } - if not assetversion_entity: - # get assettype entity from session - assettype_entity = self._get_assettype({"short": "reference"}) - - # get or create asset entity from session - asset_entity = self._get_asset({ - "name": "plateReference", - "type": assettype_entity, - "parent": self.shot_entity - }) - - # get or create assetversion entity from session - assetversion_entity = self._get_assetversion({ - "version": 0, - "asset": asset_entity - }) - - # get or create component entity - self._set_component(component_data, { - "name": name, - "version": assetversion_entity, - }) - - return assetversion_entity - - def _overwrite_members(self, entity, data): - origin_location = self._get_ftrack_location("ftrack.origin") - location = data.pop("location") - - self._remove_component_from_location(entity, location) - - entity["file_type"] = data["file_type"] - - try: - origin_location.add_component( - entity, data["file_path"] - ) - # Add components to location. - location.add_component( - entity, origin_location, recursive=True) - except Exception as __e: - print("Error: {}".format(__e)) - self._remove_component_from_location(entity, origin_location) - origin_location.add_component( - entity, data["file_path"] - ) - # Add components to location. - location.add_component( - entity, origin_location, recursive=True) - - def _remove_component_from_location(self, entity, location): - print(location) - # Removing existing members from location - components = list(entity.get("members", [])) - components += [entity] - for component in components: - for loc in component.get("component_locations", []): - if location["id"] == loc["location_id"]: - print("<< Removing component: {}".format(component)) - location.remove_component( - component, recursive=False - ) - - # Deleting existing members on component entity - for member in entity.get("members", []): - self.session.delete(member) - print("<< Deleting member: {}".format(member)) - del(member) - - self._commit() - - # Reset members in memory - if "members" in entity.keys(): - entity["members"] = [] - - def _get_assettype(self, data): - return self.session.query( - self._query("AssetType", data)).first() - - def _set_component(self, comp_data, base_data): - component_metadata = comp_data.pop("metadata", {}) - - component_entity = self.session.query( - self._query("Component", base_data) - ).first() - - if component_entity: - # overwrite existing members in component entity - # - get data for member from `ftrack.origin` location - self._overwrite_members(component_entity, comp_data) - - # Adding metadata - existing_component_metadata = component_entity["metadata"] - existing_component_metadata.update(component_metadata) - component_entity["metadata"] = existing_component_metadata - return - - assetversion_entity = base_data["version"] - location = comp_data.pop("location") - - component_entity = assetversion_entity.create_component( - comp_data["file_path"], - data=comp_data, - location=location - ) - - # Adding metadata - existing_component_metadata = component_entity["metadata"] - existing_component_metadata.update(component_metadata) - component_entity["metadata"] = existing_component_metadata - - if comp_data["name"] == "thumbnail": - self.shot_entity["thumbnail_id"] = component_entity["id"] - assetversion_entity["thumbnail_id"] = component_entity["id"] - - self._commit() - - def _get_asset(self, data): - # first find already created - asset_entity = self.session.query( - self._query("Asset", data) - ).first() - - if asset_entity: - return asset_entity - - asset_entity = self.session.create("Asset", data) - - # _commit if created - self._commit() - - return asset_entity - - def _get_assetversion(self, data): - assetversion_entity = self.session.query( - self._query("AssetVersion", data) - ).first() - - if assetversion_entity: - return assetversion_entity - - assetversion_entity = self.session.create("AssetVersion", data) - - # _commit if created - self._commit() - - return assetversion_entity - - def _commit(self): - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - # self.session.rollback() - # self.session._configure_locations() - six.reraise(tp, value, tb) - - def _get_ftrack_location(self, name=None): - name = name or self.default_location - - if name in self.ftrack_locations: - return self.ftrack_locations[name] - - location = self.session.query( - 'Location where name is "{}"'.format(name) - ).one() - self.ftrack_locations[name] = location - return location - - def _query(self, entitytype, data): - """ Generate a query expression from data supplied. - - If a value is not a string, we'll add the id of the entity to the - query. - - Args: - entitytype (str): The type of entity to query. - data (dict): The data to identify the entity. - exclusions (list): All keys to exclude from the query. - - Returns: - str: String query to use with "session.query" - """ - queries = [] - if sys.version_info[0] < 3: - for key, value in data.items(): - if not isinstance(value, (str, int)): - print("value: {}".format(value)) - if "id" in value.keys(): - queries.append( - "{0}.id is \"{1}\"".format(key, value["id"]) - ) - else: - queries.append("{0} is \"{1}\"".format(key, value)) - else: - for key, value in data.items(): - if not isinstance(value, (str, int)): - print("value: {}".format(value)) - if "id" in value.keys(): - queries.append( - "{0}.id is \"{1}\"".format(key, value["id"]) - ) - else: - queries.append("{0} is \"{1}\"".format(key, value)) - - query = ( - "select id from " + entitytype + " where " + " and ".join(queries) - ) - print(query) - return query - - -class FtrackEntityOperator: - existing_tasks = [] - - def __init__(self, session, project_entity): - self.session = session - self.project_entity = project_entity - - def commit(self): - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) - - def create_ftrack_entity(self, session, type, name, parent=None): - parent = parent or self.project_entity - entity = session.create(type, { - 'name': name, - 'parent': parent - }) - try: - session.commit() - except Exception: - tp, value, tb = sys.exc_info() - session.rollback() - session._configure_locations() - six.reraise(tp, value, tb) - return entity - - def get_ftrack_entity(self, session, type, name, parent): - query = '{} where name is "{}" and project_id is "{}"'.format( - type, name, self.project_entity["id"]) - - entity = session.query(query).first() - - # if entity doesn't exist then create one - if not entity: - entity = self.create_ftrack_entity( - session, - type, - name, - parent - ) - - return entity - - def create_parents(self, template): - parents = [] - t_split = template.split("/") - replace_patern = re.compile(r"(\[.*\])") - type_patern = re.compile(r"\[(.*)\]") - - for t_s in t_split: - match_type = type_patern.findall(t_s) - if not match_type: - raise Exception(( - "Missing correct type flag in : {}" - "/n Example: name[Type]").format( - t_s) - ) - new_name = re.sub(replace_patern, "", t_s) - f_type = match_type.pop() - - parents.append((new_name, f_type)) - - return parents - - def create_task(self, task_type, task_types, parent): - _exising_tasks = [ - child for child in parent['children'] - if child.entity_type.lower() == 'task' - ] - - # add task into existing tasks if they are not already there - for _t in _exising_tasks: - if _t in self.existing_tasks: - continue - self.existing_tasks.append(_t) - - existing_task = [ - task for task in self.existing_tasks - if task['name'].lower() in task_type.lower() - if task['parent'] == parent - ] - - if existing_task: - return existing_task.pop() - - task = self.session.create('Task', { - "name": task_type.lower(), - "parent": parent - }) - task["type"] = task_types[task_type] - - self.existing_tasks.append(task) - return task diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/panel_app.py b/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/panel_app.py deleted file mode 100644 index ce023a9e4d..0000000000 --- a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/panel_app.py +++ /dev/null @@ -1,529 +0,0 @@ -from qtpy import QtWidgets, QtCore - -import uiwidgets -import app_utils -import ftrack_lib - - -def clear_inner_modules(): - import sys - - if "ftrack_lib" in sys.modules.keys(): - del sys.modules["ftrack_lib"] - print("Ftrack Lib module removed from sys.modules") - - if "app_utils" in sys.modules.keys(): - del sys.modules["app_utils"] - print("app_utils module removed from sys.modules") - - if "uiwidgets" in sys.modules.keys(): - del sys.modules["uiwidgets"] - print("uiwidgets module removed from sys.modules") - - -class MainWindow(QtWidgets.QWidget): - - def __init__(self, klass, *args, **kwargs): - super(MainWindow, self).__init__(*args, **kwargs) - self.panel_class = klass - - def closeEvent(self, event): - # clear all temp data - print("Removing temp data") - self.panel_class.clear_temp_data() - self.panel_class.close() - clear_inner_modules() - ftrack_lib.FtrackEntityOperator.existing_tasks = [] - # now the panel can be closed - event.accept() - - -class FlameBabyPublisherPanel(object): - session = None - temp_data_dir = None - processed_components = [] - project_entity = None - task_types = {} - all_task_types = {} - - # TreeWidget - columns = { - "Sequence name": { - "columnWidth": 200, - "order": 0 - }, - "Shot name": { - "columnWidth": 200, - "order": 1 - }, - "Clip duration": { - "columnWidth": 100, - "order": 2 - }, - "Shot description": { - "columnWidth": 500, - "order": 3 - }, - "Task description": { - "columnWidth": 500, - "order": 4 - }, - } - - def __init__(self, selection): - print(selection) - - self.session = ftrack_lib.get_ftrack_session() - self.selection = selection - self.window = MainWindow(self) - - # creating ui - self.window.setMinimumSize(1500, 600) - self.window.setWindowTitle('AYON: Baby-publisher') - self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.window.setFocusPolicy(QtCore.Qt.StrongFocus) - self.window.setStyleSheet('background-color: #313131') - - self._create_project_widget() - self._create_tree_widget() - self._set_sequence_params() - self._generate_widgets() - self._generate_layouts() - self._timeline_info() - self._fix_resolution() - - self.window.show() - - def _generate_widgets(self): - with app_utils.get_config("main") as cfg_data: - cfg_d = cfg_data - - self._create_task_type_widget(cfg_d) - - # input fields - self.shot_name_label = uiwidgets.FlameLabel( - 'Shot name template', 'normal', self.window) - self.shot_name_template_input = uiwidgets.FlameLineEdit( - cfg_d["shot_name_template"], self.window) - - self.hierarchy_label = uiwidgets.FlameLabel( - 'Parents template', 'normal', self.window) - self.hierarchy_template_input = uiwidgets.FlameLineEdit( - cfg_d["hierarchy_template"], self.window) - - self.start_frame_label = uiwidgets.FlameLabel( - 'Workfile start frame', 'normal', self.window) - self.start_frame_input = uiwidgets.FlameLineEdit( - cfg_d["workfile_start_frame"], self.window) - - self.handles_label = uiwidgets.FlameLabel( - 'Shot handles', 'normal', self.window) - self.handles_input = uiwidgets.FlameLineEdit( - cfg_d["shot_handles"], self.window) - - self.width_label = uiwidgets.FlameLabel( - 'Sequence width', 'normal', self.window) - self.width_input = uiwidgets.FlameLineEdit( - str(self.seq_width), self.window) - - self.height_label = uiwidgets.FlameLabel( - 'Sequence height', 'normal', self.window) - self.height_input = uiwidgets.FlameLineEdit( - str(self.seq_height), self.window) - - self.pixel_aspect_label = uiwidgets.FlameLabel( - 'Pixel aspect ratio', 'normal', self.window) - self.pixel_aspect_input = uiwidgets.FlameLineEdit( - str(1.00), self.window) - - self.fps_label = uiwidgets.FlameLabel( - 'Frame rate', 'normal', self.window) - self.fps_input = uiwidgets.FlameLineEdit( - str(self.fps), self.window) - - # Button - self.select_all_btn = uiwidgets.FlameButton( - 'Select All', self.select_all, self.window) - - self.remove_temp_data_btn = uiwidgets.FlameButton( - 'Remove temp data', self.clear_temp_data, self.window) - - self.ftrack_send_btn = uiwidgets.FlameButton( - 'Send to Ftrack', self._send_to_ftrack, self.window) - - def _generate_layouts(self): - # left props - v_shift = 0 - prop_layout_l = QtWidgets.QGridLayout() - prop_layout_l.setHorizontalSpacing(30) - if self.project_selector_enabled: - prop_layout_l.addWidget(self.project_select_label, v_shift, 0) - prop_layout_l.addWidget(self.project_select_input, v_shift, 1) - v_shift += 1 - prop_layout_l.addWidget(self.shot_name_label, (v_shift + 0), 0) - prop_layout_l.addWidget( - self.shot_name_template_input, (v_shift + 0), 1) - prop_layout_l.addWidget(self.hierarchy_label, (v_shift + 1), 0) - prop_layout_l.addWidget( - self.hierarchy_template_input, (v_shift + 1), 1) - prop_layout_l.addWidget(self.start_frame_label, (v_shift + 2), 0) - prop_layout_l.addWidget(self.start_frame_input, (v_shift + 2), 1) - prop_layout_l.addWidget(self.handles_label, (v_shift + 3), 0) - prop_layout_l.addWidget(self.handles_input, (v_shift + 3), 1) - prop_layout_l.addWidget(self.task_type_label, (v_shift + 4), 0) - prop_layout_l.addWidget( - self.task_type_input, (v_shift + 4), 1) - - # right props - prop_widget_r = QtWidgets.QWidget(self.window) - prop_layout_r = QtWidgets.QGridLayout(prop_widget_r) - prop_layout_r.setHorizontalSpacing(30) - prop_layout_r.setAlignment( - QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) - prop_layout_r.setContentsMargins(0, 0, 0, 0) - prop_layout_r.addWidget(self.width_label, 1, 0) - prop_layout_r.addWidget(self.width_input, 1, 1) - prop_layout_r.addWidget(self.height_label, 2, 0) - prop_layout_r.addWidget(self.height_input, 2, 1) - prop_layout_r.addWidget(self.pixel_aspect_label, 3, 0) - prop_layout_r.addWidget(self.pixel_aspect_input, 3, 1) - prop_layout_r.addWidget(self.fps_label, 4, 0) - prop_layout_r.addWidget(self.fps_input, 4, 1) - - # prop layout - prop_main_layout = QtWidgets.QHBoxLayout() - prop_main_layout.addLayout(prop_layout_l, 1) - prop_main_layout.addSpacing(20) - prop_main_layout.addWidget(prop_widget_r, 1) - - # buttons layout - hbox = QtWidgets.QHBoxLayout() - hbox.addWidget(self.remove_temp_data_btn) - hbox.addWidget(self.select_all_btn) - hbox.addWidget(self.ftrack_send_btn) - - # put all layouts together - main_frame = QtWidgets.QVBoxLayout(self.window) - main_frame.setMargin(20) - main_frame.addLayout(prop_main_layout) - main_frame.addWidget(self.tree) - main_frame.addLayout(hbox) - - def _set_sequence_params(self): - for select in self.selection: - self.seq_height = select.height - self.seq_width = select.width - self.fps = float(str(select.frame_rate)[:-4]) - break - - def _create_task_type_widget(self, cfg_d): - print(self.project_entity) - self.task_types = ftrack_lib.get_project_task_types( - self.project_entity) - - self.task_type_label = uiwidgets.FlameLabel( - 'Create Task (type)', 'normal', self.window) - self.task_type_input = uiwidgets.FlamePushButtonMenu( - cfg_d["create_task_type"], self.task_types.keys(), self.window) - - def _create_project_widget(self): - import flame - # get project name from flame current project - self.project_name = flame.project.current_project.name - - # get project from ftrack - - # ftrack project name has to be the same as flame project! - query = 'Project where full_name is "{}"'.format(self.project_name) - - # globally used variables - self.project_entity = self.session.query(query).first() - - self.project_selector_enabled = bool(not self.project_entity) - - if self.project_selector_enabled: - self.all_projects = self.session.query( - "Project where status is active").all() - self.project_entity = self.all_projects[0] - project_names = [p["full_name"] for p in self.all_projects] - self.all_task_types = { - p["full_name"]: ftrack_lib.get_project_task_types(p).keys() - for p in self.all_projects - } - self.project_select_label = uiwidgets.FlameLabel( - 'Select Ftrack project', 'normal', self.window) - self.project_select_input = uiwidgets.FlamePushButtonMenu( - self.project_entity["full_name"], project_names, self.window) - self.project_select_input.selection_changed.connect( - self._on_project_changed) - - def _create_tree_widget(self): - ordered_column_labels = self.columns.keys() - for _name, _value in self.columns.items(): - ordered_column_labels.pop(_value["order"]) - ordered_column_labels.insert(_value["order"], _name) - - self.tree = uiwidgets.FlameTreeWidget( - ordered_column_labels, self.window) - - # Allow multiple items in tree to be selected - self.tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) - - # Set tree column width - for _name, _val in self.columns.items(): - self.tree.setColumnWidth( - _val["order"], - _val["columnWidth"] - ) - - # Prevent weird characters when shrinking tree columns - self.tree.setTextElideMode(QtCore.Qt.ElideNone) - - def _resolve_project_entity(self): - if self.project_selector_enabled: - selected_project_name = self.project_select_input.text() - self.project_entity = next( - (p for p in self.all_projects - if p["full_name"] in selected_project_name), - None - ) - - def _save_ui_state_to_cfg(self): - _cfg_data_back = { - "shot_name_template": self.shot_name_template_input.text(), - "workfile_start_frame": self.start_frame_input.text(), - "shot_handles": self.handles_input.text(), - "hierarchy_template": self.hierarchy_template_input.text(), - "create_task_type": self.task_type_input.text() - } - - # add cfg data back to settings.ini - app_utils.set_config(_cfg_data_back, "main") - - def _send_to_ftrack(self): - # resolve active project and add it to self.project_entity - self._resolve_project_entity() - self._save_ui_state_to_cfg() - - # get handles from gui input - handles = self.handles_input.text() - - # get frame start from gui input - frame_start = int(self.start_frame_input.text()) - - # get task type from gui input - task_type = self.task_type_input.text() - - # get resolution from gui inputs - fps = self.fps_input.text() - - entity_operator = ftrack_lib.FtrackEntityOperator( - self.session, self.project_entity) - component_creator = ftrack_lib.FtrackComponentCreator(self.session) - - if not self.temp_data_dir: - self.window.hide() - self.temp_data_dir = component_creator.generate_temp_data( - self.selection, - { - "nbHandles": handles - } - ) - self.window.show() - - # collect generated files to list data for farther use - component_creator.collect_generated_data(self.temp_data_dir) - - # Get all selected items from treewidget - for item in self.tree.selectedItems(): - # frame ranges - frame_duration = int(item.text(2)) - frame_end = frame_start + frame_duration - - # description - shot_description = item.text(3) - task_description = item.text(4) - - # other - sequence_name = item.text(0) - shot_name = item.text(1) - - thumb_fp = component_creator.get_thumb_path(shot_name) - video_fp = component_creator.get_video_path(shot_name) - - print("processed comps: {}".format(self.processed_components)) - print("processed thumb_fp: {}".format(thumb_fp)) - - processed = False - if thumb_fp not in self.processed_components: - self.processed_components.append(thumb_fp) - else: - processed = True - - print("processed: {}".format(processed)) - - # populate full shot info - shot_attributes = { - "sequence": sequence_name, - "shot": shot_name, - "task": task_type - } - - # format shot name template - _shot_name = self.shot_name_template_input.text().format( - **shot_attributes) - - # format hierarchy template - _hierarchy_text = self.hierarchy_template_input.text().format( - **shot_attributes) - print(_hierarchy_text) - - # solve parents - parents = entity_operator.create_parents(_hierarchy_text) - print(parents) - - # obtain shot parents entities - _parent = None - for _name, _type in parents: - p_entity = entity_operator.get_ftrack_entity( - self.session, - _type, - _name, - _parent - ) - print(p_entity) - _parent = p_entity - - # obtain shot ftrack entity - f_s_entity = entity_operator.get_ftrack_entity( - self.session, - "Shot", - _shot_name, - _parent - ) - print("Shot entity is: {}".format(f_s_entity)) - - if not processed: - # first create thumbnail and get version entity - assetversion_entity = component_creator.create_comonent( - f_s_entity, { - "file_path": thumb_fp - } - ) - - # secondly add video to version entity - component_creator.create_comonent( - f_s_entity, { - "file_path": video_fp, - "duration": frame_duration, - "handles": int(handles), - "fps": float(fps) - }, assetversion_entity - ) - - # create custom attributtes - custom_attrs = { - "frameStart": frame_start, - "frameEnd": frame_end, - "handleStart": int(handles), - "handleEnd": int(handles), - "resolutionWidth": int(self.width_input.text()), - "resolutionHeight": int(self.height_input.text()), - "pixelAspect": float(self.pixel_aspect_input.text()), - "fps": float(fps) - } - - # update custom attributes on shot entity - for key in custom_attrs: - f_s_entity['custom_attributes'][key] = custom_attrs[key] - - task_entity = entity_operator.create_task( - task_type, self.task_types, f_s_entity) - - # Create notes. - user = self.session.query( - "User where username is \"{}\"".format(self.session.api_user) - ).first() - - f_s_entity.create_note(shot_description, author=user) - - if task_description: - task_entity.create_note(task_description, user) - - entity_operator.commit() - - component_creator.close() - - def _fix_resolution(self): - # Center window in linux - resolution = QtWidgets.QDesktopWidget().screenGeometry() - self.window.move( - (resolution.width() / 2) - (self.window.frameSize().width() / 2), - (resolution.height() / 2) - (self.window.frameSize().height() / 2)) - - def _on_project_changed(self): - task_types = self.all_task_types[self.project_name] - self.task_type_input.set_menu_options(task_types) - - def _timeline_info(self): - # identificar as informacoes dos segmentos na timeline - for sequence in self.selection: - frame_rate = float(str(sequence.frame_rate)[:-4]) - for ver in sequence.versions: - for track in ver.tracks: - if len(track.segments) == 0 and track.hidden: - continue - for segment in track.segments: - print(segment.attributes) - if segment.name.get_value() == "": - continue - if segment.hidden.get_value() is True: - continue - # get clip frame duration - record_duration = str(segment.record_duration)[1:-1] - clip_duration = app_utils.timecode_to_frames( - record_duration, frame_rate) - - # populate shot source metadata - shot_description = "" - for attr in ["tape_name", "source_name", "head", - "tail", "file_path"]: - if not hasattr(segment, attr): - continue - _value = getattr(segment, attr) - _label = attr.replace("_", " ").capitalize() - row = "{}: {}\n".format(_label, _value) - shot_description += row - - # Add timeline segment to tree - QtWidgets.QTreeWidgetItem(self.tree, [ - sequence.name.get_value(), # seq name - segment.shot_name.get_value(), # shot name - str(clip_duration), # clip duration - shot_description, # shot description - segment.comment.get_value() # task description - ]).setFlags( - QtCore.Qt.ItemIsEditable - | QtCore.Qt.ItemIsEnabled - | QtCore.Qt.ItemIsSelectable - ) - - # Select top item in tree - self.tree.setCurrentItem(self.tree.topLevelItem(0)) - - def select_all(self, ): - self.tree.selectAll() - - def clear_temp_data(self): - import shutil - - self.processed_components = [] - - if self.temp_data_dir: - shutil.rmtree(self.temp_data_dir) - self.temp_data_dir = None - print("All Temp data were destroyed ...") - - def close(self): - self._save_ui_state_to_cfg() - self.session.close() diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/uiwidgets.py b/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/uiwidgets.py deleted file mode 100644 index 5498a49197..0000000000 --- a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/modules/uiwidgets.py +++ /dev/null @@ -1,212 +0,0 @@ -from qtpy import QtWidgets, QtCore - - -class FlameLabel(QtWidgets.QLabel): - """ - Custom Qt Flame Label Widget - - For different label looks set label_type as: - 'normal', 'background', or 'outline' - - To use: - - label = FlameLabel('Label Name', 'normal', window) - """ - - def __init__(self, label_name, label_type, parent_window, *args, **kwargs): - super(FlameLabel, self).__init__(*args, **kwargs) - - self.setText(label_name) - self.setParent(parent_window) - self.setMinimumSize(130, 28) - self.setMaximumHeight(28) - self.setFocusPolicy(QtCore.Qt.NoFocus) - - # Set label stylesheet based on label_type - - if label_type == 'normal': - self.setStyleSheet( - 'QLabel {color: #9a9a9a; border-bottom: 1px inset #282828; font: 14px "Discreet"}' # noqa - 'QLabel:disabled {color: #6a6a6a}' - ) - elif label_type == 'background': - self.setAlignment(QtCore.Qt.AlignCenter) - self.setStyleSheet( - 'color: #9a9a9a; background-color: #393939; font: 14px "Discreet"' # noqa - ) - elif label_type == 'outline': - self.setAlignment(QtCore.Qt.AlignCenter) - self.setStyleSheet( - 'color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"' # noqa - ) - - -class FlameLineEdit(QtWidgets.QLineEdit): - """ - Custom Qt Flame Line Edit Widget - - Main window should include this: - window.setFocusPolicy(QtCore.Qt.StrongFocus) - - To use: - - line_edit = FlameLineEdit('Some text here', window) - """ - - def __init__(self, text, parent_window, *args, **kwargs): - super(FlameLineEdit, self).__init__(*args, **kwargs) - - self.setText(text) - self.setParent(parent_window) - self.setMinimumHeight(28) - self.setMinimumWidth(110) - self.setStyleSheet( - 'QLineEdit {color: #9a9a9a; background-color: #373e47; selection-color: #262626; selection-background-color: #b8b1a7; font: 14px "Discreet"}' # noqa - 'QLineEdit:focus {background-color: #474e58}' # noqa - 'QLineEdit:disabled {color: #6a6a6a; background-color: #373737}' - ) - - -class FlameTreeWidget(QtWidgets.QTreeWidget): - """ - Custom Qt Flame Tree Widget - - To use: - - tree_headers = ['Header1', 'Header2', 'Header3', 'Header4'] - tree = FlameTreeWidget(tree_headers, window) - """ - - def __init__(self, tree_headers, parent_window, *args, **kwargs): - super(FlameTreeWidget, self).__init__(*args, **kwargs) - - self.setMinimumWidth(1000) - self.setMinimumHeight(300) - self.setSortingEnabled(True) - self.sortByColumn(0, QtCore.Qt.AscendingOrder) - self.setAlternatingRowColors(True) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self.setStyleSheet( - 'QTreeWidget {color: #9a9a9a; background-color: #2a2a2a; alternate-background-color: #2d2d2d; font: 14px "Discreet"}' # noqa - 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' # noqa - 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' # noqa - 'QTreeWidget::item:selected {selection-background-color: #111111}' - 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' # noqa - 'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}' - ) - self.verticalScrollBar().setStyleSheet('color: #818181') - self.horizontalScrollBar().setStyleSheet('color: #818181') - self.setHeaderLabels(tree_headers) - - -class FlameButton(QtWidgets.QPushButton): - """ - Custom Qt Flame Button Widget - - To use: - - button = FlameButton('Button Name', do_this_when_pressed, window) - """ - - def __init__(self, button_name, do_when_pressed, parent_window, - *args, **kwargs): - super(FlameButton, self).__init__(*args, **kwargs) - - self.setText(button_name) - self.setParent(parent_window) - self.setMinimumSize(QtCore.QSize(110, 28)) - self.setMaximumSize(QtCore.QSize(110, 28)) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self.clicked.connect(do_when_pressed) - self.setStyleSheet( - 'QPushButton {color: #9a9a9a; background-color: #424142; border-top: 1px inset #555555; border-bottom: 1px inset black; font: 14px "Discreet"}' # noqa - 'QPushButton:pressed {color: #d9d9d9; background-color: #4f4f4f; border-top: 1px inset #666666; font: italic}' # noqa - 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}' # noqa - ) - - -class FlamePushButton(QtWidgets.QPushButton): - """ - Custom Qt Flame Push Button Widget - - To use: - - pushbutton = FlamePushButton(' Button Name', True_or_False, window) - """ - - def __init__(self, button_name, button_checked, parent_window, - *args, **kwargs): - super(FlamePushButton, self).__init__(*args, **kwargs) - - self.setText(button_name) - self.setParent(parent_window) - self.setCheckable(True) - self.setChecked(button_checked) - self.setMinimumSize(155, 28) - self.setMaximumSize(155, 28) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self.setStyleSheet( - 'QPushButton {color: #9a9a9a; background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: .93 #424142, stop: .94 #2e3b48); text-align: left; border-top: 1px inset #555555; border-bottom: 1px inset black; font: 14px "Discreet"}' # noqa - 'QPushButton:checked {color: #d9d9d9; background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: .93 #4f4f4f, stop: .94 #5a7fb4); font: italic; border: 1px inset black; border-bottom: 1px inset #404040; border-right: 1px inset #404040}' # noqa - 'QPushButton:disabled {color: #6a6a6a; background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: .93 #383838, stop: .94 #353535); font: light; border-top: 1px solid #575757; border-bottom: 1px solid #242424; border-right: 1px solid #353535; border-left: 1px solid #353535}' # noqa - 'QToolTip {color: black; background-color: #ffffde; border: black solid 1px}' # noqa - ) - - -class FlamePushButtonMenu(QtWidgets.QPushButton): - """ - Custom Qt Flame Menu Push Button Widget - - To use: - - push_button_menu_options = ['Item 1', 'Item 2', 'Item 3', 'Item 4'] - menu_push_button = FlamePushButtonMenu('push_button_name', - push_button_menu_options, window) - - or - - push_button_menu_options = ['Item 1', 'Item 2', 'Item 3', 'Item 4'] - menu_push_button = FlamePushButtonMenu(push_button_menu_options[0], - push_button_menu_options, window) - """ - selection_changed = QtCore.Signal(str) - - def __init__(self, button_name, menu_options, parent_window, - *args, **kwargs): - super(FlamePushButtonMenu, self).__init__(*args, **kwargs) - - self.setParent(parent_window) - self.setMinimumHeight(28) - self.setMinimumWidth(110) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self.setStyleSheet( - 'QPushButton {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' # noqa - 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}' # noqa - ) - - pushbutton_menu = QtWidgets.QMenu(parent_window) - pushbutton_menu.setFocusPolicy(QtCore.Qt.NoFocus) - pushbutton_menu.setStyleSheet( - 'QMenu {color: #9a9a9a; background-color:#24303d; font: 14px "Discreet"}' # noqa - 'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}' - ) - - self._pushbutton_menu = pushbutton_menu - self.setMenu(pushbutton_menu) - self.set_menu_options(menu_options, button_name) - - def set_menu_options(self, menu_options, current_option=None): - self._pushbutton_menu.clear() - current_option = current_option or menu_options[0] - - for option in menu_options: - action = self._pushbutton_menu.addAction(option) - action.triggered.connect(self._on_action_trigger) - - if current_option is not None: - self.setText(current_option) - - def _on_action_trigger(self): - action = self.sender() - self.setText(action.text()) - self.selection_changed.emit(action.text()) diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/openpype_babypublisher.py b/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/openpype_babypublisher.py deleted file mode 100644 index 76d74b5970..0000000000 --- a/server_addon/flame/client/ayon_flame/startup/openpype_babypublisher/openpype_babypublisher.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import print_function - -import os -import sys - -# only testing dependency for nested modules in package -import six # noqa - - -SCRIPT_DIR = os.path.dirname(__file__) -PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") -sys.path.append(PACKAGE_DIR) - - -def flame_panel_executor(selection): - if "panel_app" in sys.modules.keys(): - print("panel_app module is already loaded") - del sys.modules["panel_app"] - import panel_app - reload(panel_app) # noqa - print("panel_app module removed from sys.modules") - - panel_app.FlameBabyPublisherPanel(selection) - - -def scope_sequence(selection): - import flame - return any(isinstance(item, flame.PySequence) for item in selection) - - -def get_media_panel_custom_ui_actions(): - return [ - { - "name": "AYON: Baby-publisher", - "actions": [ - { - "name": "Create Shots", - "isVisible": scope_sequence, - "execute": flame_panel_executor - } - ] - } - ] diff --git a/server_addon/flame/client/ayon_flame/startup/openpype_in_flame.py b/server_addon/flame/client/ayon_flame/startup/openpype_in_flame.py deleted file mode 100644 index 8f319a88eb..0000000000 --- a/server_addon/flame/client/ayon_flame/startup/openpype_in_flame.py +++ /dev/null @@ -1,219 +0,0 @@ -from __future__ import print_function -import sys -from qtpy import QtWidgets -from pprint import pformat -import atexit - -import ayon_flame.api as opfapi -from ayon_core.pipeline import ( - install_host, - registered_host, -) - - -def openpype_install(): - """Registering AYON in context - """ - install_host(opfapi) - print("Registered host: {}".format(registered_host())) - - -# Exception handler -def exeption_handler(exctype, value, _traceback): - """Exception handler for improving UX - - Args: - exctype (str): type of exception - value (str): exception value - tb (str): traceback to show - """ - import traceback - msg = "AYON: Python exception {} in {}".format(value, exctype) - mbox = QtWidgets.QMessageBox() - mbox.setText(msg) - mbox.setDetailedText( - pformat(traceback.format_exception(exctype, value, _traceback))) - mbox.setStyleSheet('QLabel{min-width: 800px;}') - mbox.exec_() - sys.__excepthook__(exctype, value, _traceback) - - -# add exception handler into sys module -sys.excepthook = exeption_handler - - -# register clean up logic to be called at Flame exit -def cleanup(): - """Cleaning up Flame framework context - """ - if opfapi.CTX.flame_apps: - print('`{}` cleaning up flame_apps:\n {}\n'.format( - __file__, pformat(opfapi.CTX.flame_apps))) - while len(opfapi.CTX.flame_apps): - app = opfapi.CTX.flame_apps.pop() - print('`{}` removing : {}'.format(__file__, app.name)) - del app - opfapi.CTX.flame_apps = [] - - if opfapi.CTX.app_framework: - print('openpype\t: {} cleaning up'.format( - opfapi.CTX.app_framework.bundle_name) - ) - opfapi.CTX.app_framework.save_prefs() - opfapi.CTX.app_framework = None - - -atexit.register(cleanup) - - -def load_apps(): - """Load available flame_apps into Flame framework - """ - opfapi.CTX.flame_apps.append( - opfapi.FlameMenuProjectConnect(opfapi.CTX.app_framework)) - opfapi.CTX.flame_apps.append( - opfapi.FlameMenuTimeline(opfapi.CTX.app_framework)) - opfapi.CTX.flame_apps.append( - opfapi.FlameMenuUniversal(opfapi.CTX.app_framework)) - opfapi.CTX.app_framework.log.info("Apps are loaded") - - -def project_changed_dict(info): - """Hook for project change action - - Args: - info (str): info text - """ - cleanup() - - -def app_initialized(parent=None): - """Inicialization of Framework - - Args: - parent (obj, optional): Parent object. Defaults to None. - """ - opfapi.CTX.app_framework = opfapi.FlameAppFramework() - - print("{} initializing".format( - opfapi.CTX.app_framework.bundle_name)) - - load_apps() - - -""" -Initialisation of the hook is starting from here - -First it needs to test if it can import the flame module. -This will happen only in case a project has been loaded. -Then `app_initialized` will load main Framework which will load -all menu objects as flame_apps. -""" - -try: - import flame # noqa - app_initialized(parent=None) -except ImportError: - print("!!!! not able to import flame module !!!!") - - -def rescan_hooks(): - import flame # noqa - flame.execute_shortcut('Rescan Python Hooks') - - -def _build_app_menu(app_name): - """Flame menu object generator - - Args: - app_name (str): name of menu object app - - Returns: - list: menu object - """ - menu = [] - - # first find the relative appname - app = None - for _app in opfapi.CTX.flame_apps: - if _app.__class__.__name__ == app_name: - app = _app - - if app: - menu.append(app.build_menu()) - - if opfapi.CTX.app_framework: - menu_auto_refresh = opfapi.CTX.app_framework.prefs_global.get( - 'menu_auto_refresh', {}) - if menu_auto_refresh.get('timeline_menu', True): - try: - import flame # noqa - flame.schedule_idle_event(rescan_hooks) - except ImportError: - print("!-!!! not able to import flame module !!!!") - - return menu - - -""" Flame hooks are starting here -""" - - -def project_saved(project_name, save_time, is_auto_save): - """Hook to activate when project is saved - - Args: - project_name (str): name of project - save_time (str): time when it was saved - is_auto_save (bool): autosave is on or off - """ - if opfapi.CTX.app_framework: - opfapi.CTX.app_framework.save_prefs() - - -def get_main_menu_custom_ui_actions(): - """Hook to create submenu in start menu - - Returns: - list: menu object - """ - # install openpype and the host - openpype_install() - - return _build_app_menu("FlameMenuProjectConnect") - - -def get_timeline_custom_ui_actions(): - """Hook to create submenu in timeline - - Returns: - list: menu object - """ - # install openpype and the host - openpype_install() - - return _build_app_menu("FlameMenuTimeline") - - -def get_batch_custom_ui_actions(): - """Hook to create submenu in batch - - Returns: - list: menu object - """ - # install openpype and the host - openpype_install() - - return _build_app_menu("FlameMenuUniversal") - - -def get_media_panel_custom_ui_actions(): - """Hook to create submenu in desktop - - Returns: - list: menu object - """ - # install openpype and the host - openpype_install() - - return _build_app_menu("FlameMenuUniversal") diff --git a/server_addon/flame/client/ayon_flame/version.py b/server_addon/flame/client/ayon_flame/version.py deleted file mode 100644 index 68bdb6e6a0..0000000000 --- a/server_addon/flame/client/ayon_flame/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring AYON addon 'flame' version.""" -__version__ = "0.2.1" diff --git a/server_addon/flame/package.py b/server_addon/flame/package.py deleted file mode 100644 index b25a514a9f..0000000000 --- a/server_addon/flame/package.py +++ /dev/null @@ -1,10 +0,0 @@ -name = "flame" -title = "Flame" -version = "0.2.1" - -client_dir = "ayon_flame" - -ayon_required_addons = { - "core": ">0.3.2", -} -ayon_compatible_addons = {} diff --git a/server_addon/flame/server/__init__.py b/server_addon/flame/server/__init__.py deleted file mode 100644 index 4aa46617ee..0000000000 --- a/server_addon/flame/server/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Type - -from ayon_server.addons import BaseServerAddon - -from .settings import FlameSettings, DEFAULT_VALUES - - -class FlameAddon(BaseServerAddon): - settings_model: Type[FlameSettings] = FlameSettings - - async def get_default_settings(self): - settings_model_cls = self.get_settings_model() - return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/flame/server/settings/__init__.py b/server_addon/flame/server/settings/__init__.py deleted file mode 100644 index 39b8220d40..0000000000 --- a/server_addon/flame/server/settings/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .main import ( - FlameSettings, - DEFAULT_VALUES, -) - - -__all__ = ( - "FlameSettings", - "DEFAULT_VALUES", -) diff --git a/server_addon/flame/server/settings/create_plugins.py b/server_addon/flame/server/settings/create_plugins.py deleted file mode 100644 index 2f17ec40c4..0000000000 --- a/server_addon/flame/server/settings/create_plugins.py +++ /dev/null @@ -1,119 +0,0 @@ -from ayon_server.settings import BaseSettingsModel, SettingsField - - -class CreateShotClipModel(BaseSettingsModel): - hierarchy: str = SettingsField( - "shot", - title="Shot parent hierarchy", - section="Shot Hierarchy And Rename Settings" - ) - useShotName: bool = SettingsField( - True, - title="Use Shot Name", - ) - clipRename: bool = SettingsField( - False, - title="Rename clips", - ) - clipName: str = SettingsField( - "{sequence}{shot}", - title="Clip name template" - ) - segmentIndex: bool = SettingsField( - True, - title="Accept segment order" - ) - countFrom: int = SettingsField( - 10, - title="Count sequence from" - ) - countSteps: int = SettingsField( - 10, - title="Stepping number" - ) - - folder: str = SettingsField( - "shots", - title="{folder}", - section="Shot Template Keywords" - ) - episode: str = SettingsField( - "ep01", - title="{episode}" - ) - sequence: str = SettingsField( - "a", - title="{sequence}" - ) - track: str = SettingsField( - "{_track_}", - title="{track}" - ) - shot: str = SettingsField( - "####", - title="{shot}" - ) - - vSyncOn: bool = SettingsField( - False, - title="Enable Vertical Sync", - section="Vertical Synchronization Of Attributes" - ) - - workfileFrameStart: int = SettingsField( - 1001, - title="Workfiles Start Frame", - section="Shot Attributes" - ) - handleStart: int = SettingsField( - 10, - title="Handle start (head)" - ) - handleEnd: int = SettingsField( - 10, - title="Handle end (tail)" - ) - includeHandles: bool = SettingsField( - False, - title="Enable handles including" - ) - retimedHandles: bool = SettingsField( - True, - title="Enable retimed handles" - ) - retimedFramerange: bool = SettingsField( - True, - title="Enable retimed shot frameranges" - ) - - -class CreatePluginsModel(BaseSettingsModel): - CreateShotClip: CreateShotClipModel = SettingsField( - default_factory=CreateShotClipModel, - title="Create Shot Clip" - ) - - -DEFAULT_CREATE_SETTINGS = { - "CreateShotClip": { - "hierarchy": "{folder}/{sequence}", - "useShotName": True, - "clipRename": False, - "clipName": "{sequence}{shot}", - "segmentIndex": True, - "countFrom": 10, - "countSteps": 10, - "folder": "shots", - "episode": "ep01", - "sequence": "a", - "track": "{_track_}", - "shot": "####", - "vSyncOn": False, - "workfileFrameStart": 1001, - "handleStart": 5, - "handleEnd": 5, - "includeHandles": False, - "retimedHandles": True, - "retimedFramerange": True - } -} diff --git a/server_addon/flame/server/settings/imageio.py b/server_addon/flame/server/settings/imageio.py deleted file mode 100644 index abd058ee13..0000000000 --- a/server_addon/flame/server/settings/imageio.py +++ /dev/null @@ -1,149 +0,0 @@ -from pydantic import validator -from ayon_server.settings import ( - BaseSettingsModel, - SettingsField, - ensure_unique_names, -) - - -class ImageIOFileRuleModel(BaseSettingsModel): - name: str = SettingsField("", title="Rule name") - pattern: str = SettingsField("", title="Regex pattern") - colorspace: str = SettingsField("", title="Colorspace name") - ext: str = SettingsField("", title="File extension") - - -class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = SettingsField(False) - rules: list[ImageIOFileRuleModel] = SettingsField( - default_factory=list, - title="Rules" - ) - - @validator("rules") - def validate_unique_outputs(cls, value): - ensure_unique_names(value) - return value - - -class ImageIORemappingRulesModel(BaseSettingsModel): - host_native_name: str = SettingsField( - title="Application native colorspace name" - ) - ocio_name: str = SettingsField(title="OCIO colorspace name") - - -class ImageIORemappingModel(BaseSettingsModel): - rules: list[ImageIORemappingRulesModel] = SettingsField( - default_factory=list - ) - - -class ImageIOConfigModel(BaseSettingsModel): - """[DEPRECATED] Addon OCIO config settings. Please set the OCIO config - path in the Core addon profiles here - (ayon+settings://core/imageio/ocio_config_profiles). - """ - - override_global_config: bool = SettingsField( - False, - title="Override global OCIO config", - description=( - "DEPRECATED functionality. Please set the OCIO config path in the " - "Core addon profiles here (ayon+settings://core/imageio/" - "ocio_config_profiles)." - ), - ) - filepath: list[str] = SettingsField( - default_factory=list, - title="Config path", - description=( - "DEPRECATED functionality. Please set the OCIO config path in the " - "Core addon profiles here (ayon+settings://core/imageio/" - "ocio_config_profiles)." - ), - ) - - -class ProfileNamesMappingInputsModel(BaseSettingsModel): - _layout = "expanded" - - flameName: str = SettingsField("", title="Flame name") - ocioName: str = SettingsField("", title="OCIO name") - - -class ProfileNamesMappingModel(BaseSettingsModel): - _layout = "expanded" - - inputs: list[ProfileNamesMappingInputsModel] = SettingsField( - default_factory=list, - title="Profile names mapping" - ) - - -class ImageIOProjectModel(BaseSettingsModel): - colourPolicy: str = SettingsField( - "ACES 1.1", - title="Colour Policy (name or path)", - section="Project" - ) - frameDepth: str = SettingsField( - "16-bit fp", - title="Image Depth" - ) - fieldDominance: str = SettingsField( - "PROGRESSIVE", - title="Field Dominance" - ) - - -class FlameImageIOModel(BaseSettingsModel): - _isGroup = True - activate_host_color_management: bool = SettingsField( - True, title="Enable Color Management" - ) - remapping: ImageIORemappingModel = SettingsField( - title="Remapping colorspace names", - default_factory=ImageIORemappingModel - ) - ocio_config: ImageIOConfigModel = SettingsField( - default_factory=ImageIOConfigModel, - title="OCIO config" - ) - file_rules: ImageIOFileRulesModel = SettingsField( - default_factory=ImageIOFileRulesModel, - title="File Rules" - ) - # NOTE 'project' attribute was expanded to this model but that caused - # inconsistency with v3 settings and harder conversion handling - # - it can be moved back but keep in mind that it must be handled in v3 - # conversion script too - project: ImageIOProjectModel = SettingsField( - default_factory=ImageIOProjectModel, - title="Project" - ) - profilesMapping: ProfileNamesMappingModel = SettingsField( - default_factory=ProfileNamesMappingModel, - title="Profile names mapping" - ) - - -DEFAULT_IMAGEIO_SETTINGS = { - "project": { - "colourPolicy": "ACES 1.1", - "frameDepth": "16-bit fp", - "fieldDominance": "PROGRESSIVE" - }, - "profilesMapping": { - "inputs": [ - { - "flameName": "ACEScg", - "ocioName": "ACES - ACEScg" - }, - { - "flameName": "Rec.709 video", - "ocioName": "Output - Rec.709" - } - ] - } -} diff --git a/server_addon/flame/server/settings/loader_plugins.py b/server_addon/flame/server/settings/loader_plugins.py deleted file mode 100644 index e616f442b5..0000000000 --- a/server_addon/flame/server/settings/loader_plugins.py +++ /dev/null @@ -1,103 +0,0 @@ -from ayon_server.settings import SettingsField, BaseSettingsModel - - -class LoadClipModel(BaseSettingsModel): - enabled: bool = SettingsField(True) - - product_types: list[str] = SettingsField( - default_factory=list, - title="Product types" - ) - reel_group_name: str = SettingsField( - "OpenPype_Reels", - title="Reel group name" - ) - reel_name: str = SettingsField( - "Loaded", - title="Reel name" - ) - - clip_name_template: str = SettingsField( - "{folder[name]}_{product[name]}<_{output}>", - title="Clip name template" - ) - layer_rename_template: str = SettingsField( - "", title="Layer name template" - ) - layer_rename_patterns: list[str] = SettingsField( - default_factory=list, - title="Layer rename patters", - ) - - -class LoadClipBatchModel(BaseSettingsModel): - enabled: bool = SettingsField(True) - product_types: list[str] = SettingsField( - default_factory=list, - title="Product types" - ) - reel_name: str = SettingsField( - "OP_LoadedReel", - title="Reel name" - ) - clip_name_template: str = SettingsField( - "{batch}_{folder[name]}_{product[name]}<_{output}>", - title="Clip name template" - ) - layer_rename_template: str = SettingsField( - "", title="Layer name template" - ) - layer_rename_patterns: list[str] = SettingsField( - default_factory=list, - title="Layer rename patters", - ) - - -class LoaderPluginsModel(BaseSettingsModel): - LoadClip: LoadClipModel = SettingsField( - default_factory=LoadClipModel, - title="Load Clip" - ) - LoadClipBatch: LoadClipBatchModel = SettingsField( - default_factory=LoadClipBatchModel, - title="Load as clip to current batch" - ) - - -DEFAULT_LOADER_SETTINGS = { - "LoadClip": { - "enabled": True, - "product_types": [ - "render2d", - "source", - "plate", - "render", - "review" - ], - "reel_group_name": "OpenPype_Reels", - "reel_name": "Loaded", - "clip_name_template": "{folder[name]}_{product[name]}<_{output}>", - "layer_rename_template": "{folder[name]}_{product[name]}<_{output}>", - "layer_rename_patterns": [ - "rgb", - "rgba" - ] - }, - "LoadClipBatch": { - "enabled": True, - "product_types": [ - "render2d", - "source", - "plate", - "render", - "review" - ], - "reel_name": "OP_LoadedReel", - "clip_name_template": "{batch}_{folder[name]}_{product[name]}<_{output}>", - "layer_rename_template": "{folder[name]}_{product[name]}<_{output}>", - "layer_rename_patterns": [ - "rgb", - "rgba" - ] - } -} diff --git a/server_addon/flame/server/settings/main.py b/server_addon/flame/server/settings/main.py deleted file mode 100644 index c838ee9646..0000000000 --- a/server_addon/flame/server/settings/main.py +++ /dev/null @@ -1,33 +0,0 @@ -from ayon_server.settings import BaseSettingsModel, SettingsField - -from .imageio import FlameImageIOModel, DEFAULT_IMAGEIO_SETTINGS -from .create_plugins import CreatePluginsModel, DEFAULT_CREATE_SETTINGS -from .publish_plugins import PublishPluginsModel, DEFAULT_PUBLISH_SETTINGS -from .loader_plugins import LoaderPluginsModel, DEFAULT_LOADER_SETTINGS - - -class FlameSettings(BaseSettingsModel): - imageio: FlameImageIOModel = SettingsField( - default_factory=FlameImageIOModel, - title="Color Management (ImageIO)" - ) - create: CreatePluginsModel = SettingsField( - default_factory=CreatePluginsModel, - title="Create plugins" - ) - publish: PublishPluginsModel = SettingsField( - default_factory=PublishPluginsModel, - title="Publish plugins" - ) - load: LoaderPluginsModel = SettingsField( - default_factory=LoaderPluginsModel, - title="Loader plugins" - ) - - -DEFAULT_VALUES = { - "imageio": DEFAULT_IMAGEIO_SETTINGS, - "create": DEFAULT_CREATE_SETTINGS, - "publish": DEFAULT_PUBLISH_SETTINGS, - "load": DEFAULT_LOADER_SETTINGS -} diff --git a/server_addon/flame/server/settings/publish_plugins.py b/server_addon/flame/server/settings/publish_plugins.py deleted file mode 100644 index b34083b4e2..0000000000 --- a/server_addon/flame/server/settings/publish_plugins.py +++ /dev/null @@ -1,196 +0,0 @@ -from ayon_server.settings import ( - BaseSettingsModel, - SettingsField, - task_types_enum, -) - - -class XMLPresetAttrsFromCommentsModel(BaseSettingsModel): - _layout = "expanded" - name: str = SettingsField("", title="Attribute name") - type: str = SettingsField( - default_factory=str, - title="Attribute type", - enum_resolver=lambda: ["number", "float", "string"] - ) - - -class AddTasksModel(BaseSettingsModel): - _layout = "expanded" - name: str = SettingsField("", title="Task name") - type: str = SettingsField( - default_factory=str, - title="Task type", - enum_resolver=task_types_enum - ) - create_batch_group: bool = SettingsField( - True, - title="Create batch group" - ) - - -class CollectTimelineInstancesModel(BaseSettingsModel): - _isGroup = True - - xml_preset_attrs_from_comments: list[XMLPresetAttrsFromCommentsModel] = ( - SettingsField( - default_factory=list, - title="XML presets attributes parsable from segment comments" - ) - ) - add_tasks: list[AddTasksModel] = SettingsField( - default_factory=list, - title="Add tasks" - ) - - -class ExportPresetsMappingModel(BaseSettingsModel): - _layout = "expanded" - - name: str = SettingsField( - ..., - title="Name" - ) - active: bool = SettingsField(True, title="Is active") - export_type: str = SettingsField( - "File Sequence", - title="Eport clip type", - enum_resolver=lambda: ["Movie", "File Sequence", "Sequence Publish"] - ) - ext: str = SettingsField("exr", title="Output extension") - xml_preset_file: str = SettingsField( - "OpenEXR (16-bit fp DWAA).xml", - title="XML preset file (with ext)" - ) - colorspace_out: str = SettingsField( - "ACES - ACEScg", - title="Output color (imageio)" - ) - # TODO remove when resolved or v3 is not a thing anymore - # NOTE next 4 attributes were grouped under 'other_parameters' but that - # created inconsistency with v3 settings and harder conversion handling - # - it can be moved back but keep in mind that it must be handled in v3 - # conversion script too - xml_preset_dir: str = SettingsField( - "", - title="XML preset directory" - ) - parsed_comment_attrs: bool = SettingsField( - True, - title="Parsed comment attributes" - ) - representation_add_range: bool = SettingsField( - True, - title="Add range to representation name" - ) - representation_tags: list[str] = SettingsField( - default_factory=list, - title="Representation tags" - ) - load_to_batch_group: bool = SettingsField( - True, - title="Load to batch group reel" - ) - batch_group_loader_name: str = SettingsField( - "LoadClipBatch", - title="Use loader name" - ) - filter_path_regex: str = SettingsField( - ".*", - title="Regex in clip path" - ) - - -class ExtractProductResourcesModel(BaseSettingsModel): - _isGroup = True - - keep_original_representation: bool = SettingsField( - False, - title="Publish clip's original media" - ) - export_presets_mapping: list[ExportPresetsMappingModel] = SettingsField( - default_factory=list, - title="Export presets mapping" - ) - - -class IntegrateBatchGroupModel(BaseSettingsModel): - enabled: bool = SettingsField( - False, - title="Enabled" - ) - - -class PublishPluginsModel(BaseSettingsModel): - CollectTimelineInstances: CollectTimelineInstancesModel = SettingsField( - default_factory=CollectTimelineInstancesModel, - title="Collect Timeline Instances" - ) - - ExtractProductResources: ExtractProductResourcesModel = SettingsField( - default_factory=ExtractProductResourcesModel, - title="Extract Product Resources" - ) - - IntegrateBatchGroup: IntegrateBatchGroupModel = SettingsField( - default_factory=IntegrateBatchGroupModel, - title="IntegrateBatchGroup" - ) - - -DEFAULT_PUBLISH_SETTINGS = { - "CollectTimelineInstances": { - "xml_preset_attrs_from_comments": [ - { - "name": "width", - "type": "number" - }, - { - "name": "height", - "type": "number" - }, - { - "name": "pixelRatio", - "type": "float" - }, - { - "name": "resizeType", - "type": "string" - }, - { - "name": "resizeFilter", - "type": "string" - } - ], - "add_tasks": [ - { - "name": "compositing", - "type": "Compositing", - "create_batch_group": True - } - ] - }, - "ExtractProductResources": { - "keep_original_representation": False, - "export_presets_mapping": [ - { - "name": "exr16fpdwaa", - "active": True, - "export_type": "File Sequence", - "ext": "exr", - "xml_preset_file": "OpenEXR (16-bit fp DWAA).xml", - "colorspace_out": "ACES - ACEScg", - "xml_preset_dir": "", - "parsed_comment_attrs": True, - "representation_add_range": True, - "representation_tags": [], - "load_to_batch_group": True, - "batch_group_loader_name": "LoadClipBatch", - "filter_path_regex": ".*" - } - ] - }, - "IntegrateBatchGroup": { - "enabled": False - } -}