From 48c5372ba53dc17d354439158d462359167e7b01 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 16:29:25 +0200 Subject: [PATCH 01/58] Flame adding flame to ftrack script --- .../openpype_flame_to_ftrack.py | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py new file mode 100644 index 0000000000..5f9a78ce16 --- /dev/null +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -0,0 +1,178 @@ +from __future__ import print_function +from PySide2 import QtWidgets, QtCore + + +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"}' + 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' + 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' + 'QTreeWidget::item:selected {selection-background-color: #111111}' + 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' + '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"}' + 'QPushButton:pressed {color: #d9d9d9; background-color: #4f4f4f; border-top: 1px inset #666666; font: italic}' + 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') + + +def main_window(selection): + + def timeline_info(selection): + import flame + + # identificar as informacoes dos segmentos na timeline + + for sequence in selection: + for ver in sequence.versions: + for tracks in ver.tracks: + for segment in tracks.segments: + # Add timeline segment to tree + QtWidgets.QTreeWidgetItem(tree, [ + str(sequence.name)[1:-1], + str(segment.shot_name)[1:-1], + 'Compositing', + 'Ready to Start', + 'Tape: {} - Duration {}'.format( + segment.tape_name, + str(segment.source_duration)[4:-1] + ), + str(segment.comment)[1:-1] + ]).setFlags( + QtCore.Qt.ItemIsEditable + | QtCore.Qt.ItemIsEnabled + | QtCore.Qt.ItemIsSelectable + ) + # Select top item in tree + + tree.setCurrentItem(tree.topLevelItem(0)) + + def select_all(): + + tree.selectAll() + + def send_to_ftrack(): + # Get all selected items from treewidget + clip_info = '' + + for item in tree.selectedItems(): + tree_line = [ + item.text(0), + item.text(1), + item.text(2), + item.text(3), + item.text(4), + item.text(5) + ] + print(tree_line) + clip_info += tree_line + '\n' + + # creating ui + window = QtWidgets.QWidget() + window.setMinimumSize(500, 350) + window.setWindowTitle('Sequence Shots to Ftrack') + window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + window.setAttribute(QtCore.Qt.WA_DeleteOnClose) + window.setStyleSheet('background-color: #313131') + + # Center window in linux + + resolution = QtWidgets.QDesktopWidget().screenGeometry() + window.move((resolution.width() / 2) - (window.frameSize().width() / 2), + (resolution.height() / 2) - (window.frameSize().height() / 2)) + + ## TreeWidget + headers = ['Sequence Name', 'Shot Name', 'Task Type', + 'Task Status', 'Shot Description', 'Task Description'] + tree = FlameTreeWidget(headers, window) + + # Allow multiple items in tree to be selected + tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + + # Set tree column width + tree.setColumnWidth(0, 200) + tree.setColumnWidth(1, 100) + tree.setColumnWidth(2, 100) + tree.setColumnWidth(3, 120) + tree.setColumnWidth(4, 270) + tree.setColumnWidth(5, 270) + + # Prevent weird characters when shrinking tree columns + tree.setTextElideMode(QtCore.Qt.ElideNone) + + ## Button + select_all_btn = FlameButton('Select All', select_all, window) + copy_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) + + ## Window Layout + gridbox = QtWidgets.QGridLayout() + gridbox.setMargin(20) + gridbox.addWidget(tree, 1, 0, 5, 1) + gridbox.addWidget(select_all_btn, 1, 1) + gridbox.addWidget(copy_btn, 2, 1) + + window.setLayout(gridbox) + window.show() + + timeline_info(selection) + + return window + + +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": "OpenPype: Ftrack", + "actions": [ + { + "name": "Create Shots", + "isVisible": scope_sequence, + "execute": main_window + } + ] + } + + ] From 051e74a448ef2edb765fb24e3357dfb6ed7b108d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 16:50:14 +0200 Subject: [PATCH 02/58] testing ftrack --- .../openpype_flame_to_ftrack.py | 60 +++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index 5f9a78ce16..2f40c25e88 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -1,6 +1,6 @@ from __future__ import print_function from PySide2 import QtWidgets, QtCore - +from pprint import pformat class FlameTreeWidget(QtWidgets.QTreeWidget): """ @@ -61,7 +61,6 @@ def main_window(selection): import flame # identificar as informacoes dos segmentos na timeline - for sequence in selection: for ver in sequence.versions: for tracks in ver.tracks: @@ -91,8 +90,59 @@ def main_window(selection): tree.selectAll() def send_to_ftrack(): + import ftrack_api + + def validate_credentials(url, user, api): + 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: + session = ftrack_api.Session( + server_url=url, + api_user=user, + api_key=api + ) + session.close() + 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 + print( + 'Credentials Username: "{}", API key: "{}" are valid.'.format( + user, api) + ) + return True + + # fill your own credentials + url = "" + user = "" + api = "" + + if validate_credentials(url, user, api): + print("validation of ftrack went trough") + # Get all selected items from treewidget - clip_info = '' + clips_info = [] for item in tree.selectedItems(): tree_line = [ @@ -104,7 +154,9 @@ def main_window(selection): item.text(5) ] print(tree_line) - clip_info += tree_line + '\n' + clips_info.append(tree_line) + + print("selected clips: {}".format(pformat(clips_info))) # creating ui window = QtWidgets.QWidget() From eb63e6910f2a6744c8c38eeeb569ced1c57d1fd5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 17:16:53 +0200 Subject: [PATCH 03/58] flame to ftrack: adding maintained session --- .../openpype_flame_to_ftrack.py | 150 ++++++++++-------- 1 file changed, 85 insertions(+), 65 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index 2f40c25e88..7460313daa 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -1,6 +1,75 @@ from __future__ import print_function from PySide2 import QtWidgets, QtCore from pprint import pformat +from contextlib import contextmanager + +@contextmanager +def maintained_ftrack_session(): + import ftrack_api + import os + + def validate_credentials(url, user, api): + 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: + session = ftrack_api.Session( + server_url=url, + api_user=user, + api_key=api + ) + session.close() + 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 + print( + 'Credentials Username: "{}", API key: "{}" are valid.'.format( + user, api) + ) + return True + + # fill your own credentials + url = os.getenv("FTRACK_SERVER") + user = os.getenv("FTRACK_API_USER") + api = os.getenv("FTRACK_API_KEY") + + try: + assert validate_credentials(url, user, api), ( + "Ftrack credentials failed") + # open ftrack session + session = ftrack_api.Session( + server_url=url, + api_user=user, + api_key=api + ) + yield session + except Exception as _E: + ConnectionRefusedError( + "ERROR: {}".format(_E)) + finally: + # close the session + session.close() class FlameTreeWidget(QtWidgets.QTreeWidget): """ @@ -90,73 +159,24 @@ def main_window(selection): tree.selectAll() def send_to_ftrack(): - import ftrack_api + with maintained_ftrack_session() as session: + print("Ftrack session is: {}".format(session)) + # Get all selected items from treewidget + clips_info = [] - def validate_credentials(url, user, api): - 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 + for item in tree.selectedItems(): + tree_line = [ + item.text(0), + item.text(1), + item.text(2), + item.text(3), + item.text(4), + item.text(5) + ] + print(tree_line) + clips_info.append(tree_line) - try: - session = ftrack_api.Session( - server_url=url, - api_user=user, - api_key=api - ) - session.close() - 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 - print( - 'Credentials Username: "{}", API key: "{}" are valid.'.format( - user, api) - ) - return True - - # fill your own credentials - url = "" - user = "" - api = "" - - if validate_credentials(url, user, api): - print("validation of ftrack went trough") - - # Get all selected items from treewidget - clips_info = [] - - for item in tree.selectedItems(): - tree_line = [ - item.text(0), - item.text(1), - item.text(2), - item.text(3), - item.text(4), - item.text(5) - ] - print(tree_line) - clips_info.append(tree_line) - - print("selected clips: {}".format(pformat(clips_info))) + print("selected clips: {}".format(pformat(clips_info))) # creating ui window = QtWidgets.QWidget() From 6700b0896d0066cfc02ee660835ddb2168ec3065 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 17:47:53 +0200 Subject: [PATCH 04/58] improving shot name and ftrack test create --- .../openpype_flame_to_ftrack.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index 7460313daa..c2934f0dc1 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -136,8 +136,8 @@ def main_window(selection): for segment in tracks.segments: # Add timeline segment to tree QtWidgets.QTreeWidgetItem(tree, [ - str(sequence.name)[1:-1], - str(segment.shot_name)[1:-1], + str(sequence.name), + str(segment.name), 'Compositing', 'Ready to Start', 'Tape: {} - Duration {}'.format( @@ -159,12 +159,46 @@ def main_window(selection): tree.selectAll() def send_to_ftrack(): + import flame + import six + import sys + + def create_ftrack_entity(session, name, parent): + 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 + with maintained_ftrack_session() as session: print("Ftrack session is: {}".format(session)) + + # get project name from flame current project + 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(project_name) + f_project = session.query(query).one() + print("Ftrack project is: {}".format(f_project)) + # Get all selected items from treewidget clips_info = [] for item in tree.selectedItems(): + f_entity = create_ftrack_entity( + session, + item.text(1), + f_project + ) + print("Shot entity is: {}".format(f_entity)) + tree_line = [ item.text(0), item.text(1), From 1532b9caab674a8d8f311470e551107945e692d0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 17:53:34 +0200 Subject: [PATCH 05/58] fix name and duration population --- .../hosts/flame/utility_scripts/openpype_flame_to_ftrack.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index c2934f0dc1..bd529cda1d 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -136,13 +136,13 @@ def main_window(selection): for segment in tracks.segments: # Add timeline segment to tree QtWidgets.QTreeWidgetItem(tree, [ - str(sequence.name), - str(segment.name), + str(sequence.name)[1:-1], + str(segment.name)[1:-1], 'Compositing', 'Ready to Start', 'Tape: {} - Duration {}'.format( segment.tape_name, - str(segment.source_duration)[4:-1] + str(segment.record_duration)[4:-1] ), str(segment.comment)[1:-1] ]).setFlags( From e24716cb8cb1e8546b702ae31a9a4185b42d1982 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Oct 2021 18:03:50 +0200 Subject: [PATCH 06/58] test ftrack entity create fixes --- .../hosts/flame/utility_scripts/openpype_flame_to_ftrack.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index bd529cda1d..aaf4211f38 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -65,7 +65,7 @@ def maintained_ftrack_session(): ) yield session except Exception as _E: - ConnectionRefusedError( + print( "ERROR: {}".format(_E)) finally: # close the session @@ -163,7 +163,7 @@ def main_window(selection): import six import sys - def create_ftrack_entity(session, name, parent): + def create_ftrack_entity(session, type, name, parent): entity = session.create(type, { 'name': name, 'parent': parent @@ -194,6 +194,7 @@ def main_window(selection): for item in tree.selectedItems(): f_entity = create_ftrack_entity( session, + "Shot", item.text(1), f_project ) @@ -221,7 +222,6 @@ def main_window(selection): window.setStyleSheet('background-color: #313131') # Center window in linux - resolution = QtWidgets.QDesktopWidget().screenGeometry() window.move((resolution.width() / 2) - (window.frameSize().width() / 2), (resolution.height() / 2) - (window.frameSize().height() / 2)) From e1dc75f3a6addab2487e481f9a2258d0af93cf97 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Oct 2021 16:13:28 +0200 Subject: [PATCH 07/58] flame: expanding columns and collecttors --- .../openpype_flame_to_ftrack.py | 186 ++++++++++++++++-- 1 file changed, 165 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index aaf4211f38..f922998a2e 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -3,6 +3,12 @@ from PySide2 import QtWidgets, QtCore from pprint import pformat from contextlib import contextmanager +# Constants +WORKFILE_START_FRAME = 1001 +HIERARCHY_TEMPLATE = "shots/{sequence}" +CREATE_TASK_TYPE = "Compositing" + + @contextmanager def maintained_ftrack_session(): import ftrack_api @@ -71,6 +77,63 @@ def maintained_ftrack_session(): # close the session session.close() + +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"}' + 'QLabel:disabled {color: #6a6a6a}') + elif label_type == 'background': + self.setAlignment(QtCore.Qt.AlignCenter) + self.setStyleSheet('color: #9a9a9a; background-color: #393939; font: 14px "Discreet"') + elif label_type == 'outline': + self.setAlignment(QtCore.Qt.AlignCenter) + self.setStyleSheet('color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"') + + +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"}' + 'QLineEdit:focus {background-color: #474e58}' + 'QLineEdit:disabled {color: #6a6a6a; background-color: #373737}') + + class FlameTreeWidget(QtWidgets.QTreeWidget): """ Custom Qt Flame Tree Widget @@ -125,33 +188,74 @@ class FlameButton(QtWidgets.QPushButton): def main_window(selection): + 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 timecode_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(timecode_to_frames(timecode, start='00:00:00:00'))) + + return frames def timeline_info(selection): import flame # identificar as informacoes dos segmentos na timeline for sequence in selection: + frame_rate = float(str(sequence.frame_rate)[:-4]) for ver in sequence.versions: for tracks in ver.tracks: for segment in tracks.segments: + print(segment.type) + # get clip frame duration + record_duration = str(segment.record_duration)[1:-1] + clip_duration = 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(tree, [ - str(sequence.name)[1:-1], - str(segment.name)[1:-1], - 'Compositing', - 'Ready to Start', - 'Tape: {} - Duration {}'.format( - segment.tape_name, - str(segment.record_duration)[4:-1] - ), - str(segment.comment)[1:-1] + str(sequence.name)[1:-1], # seq + str(segment.shot_name)[1:-1], # shot + CREATE_TASK_TYPE, # task type + str(WORKFILE_START_FRAME), # start frame + str(clip_duration), # clip duration + "0:0", # handles + shot_description, # shot description + str(segment.comment)[1:-1] # task description ]).setFlags( QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable ) - # Select top item in tree + # Select top item in tree tree.setCurrentItem(tree.topLevelItem(0)) def select_all(): @@ -206,7 +310,9 @@ def main_window(selection): item.text(2), item.text(3), item.text(4), - item.text(5) + item.text(5), + item.text(6), + item.text(7) ] print(tree_line) clips_info.append(tree_line) @@ -226,25 +332,61 @@ def main_window(selection): window.move((resolution.width() / 2) - (window.frameSize().width() / 2), (resolution.height() / 2) - (window.frameSize().height() / 2)) - ## TreeWidget - headers = ['Sequence Name', 'Shot Name', 'Task Type', - 'Task Status', 'Shot Description', 'Task Description'] - tree = FlameTreeWidget(headers, window) + # TreeWidget + columns = { + "Sequence name": { + "columnWidth": 100, + "order": 0 + }, + "Shot name": { + "columnWidth": 100, + "order": 1 + }, + "Task type": { + "columnWidth": 100, + "order": 2 + }, + "Start frame": { + "columnWidth": 100, + "order": 3 + }, + "Clip duration": { + "columnWidth": 100, + "order": 4 + }, + "Handles": { + "columnWidth": 100, + "order": 5 + }, + "Shot description": { + "columnWidth": 300, + "order": 6 + }, + "Task description": { + "columnWidth": 300, + "order": 7 + }, + } + tree = FlameTreeWidget(columns.keys(), window) # Allow multiple items in tree to be selected tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) # Set tree column width - tree.setColumnWidth(0, 200) - tree.setColumnWidth(1, 100) - tree.setColumnWidth(2, 100) - tree.setColumnWidth(3, 120) - tree.setColumnWidth(4, 270) - tree.setColumnWidth(5, 270) + for _name, _val in columns.items(): + tree.setColumnWidth( + _val["order"], + _val["columnWidth"] + ) # Prevent weird characters when shrinking tree columns tree.setTextElideMode(QtCore.Qt.ElideNone) + # input fields + hierarchy_label = FlameLabel( + 'Parents template', 'normal', window) + hierarchy_template = FlameLineEdit(HIERARCHY_TEMPLATE, window) + ## Button select_all_btn = FlameButton('Select All', select_all, window) copy_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) @@ -252,6 +394,8 @@ def main_window(selection): ## Window Layout gridbox = QtWidgets.QGridLayout() gridbox.setMargin(20) + gridbox.addWidget(hierarchy_label, 0, 0) + gridbox.addWidget(hierarchy_template, 0, 1) gridbox.addWidget(tree, 1, 0, 5, 1) gridbox.addWidget(select_all_btn, 1, 1) gridbox.addWidget(copy_btn, 2, 1) From 319dcaf2e6b7b0c39eb74211c59a3665dc2c4008 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Oct 2021 17:42:26 +0200 Subject: [PATCH 08/58] flame: tuning the shot data population --- .../openpype_flame_to_ftrack.py | 76 ++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index f922998a2e..9364d8d57f 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -242,7 +242,7 @@ def main_window(selection): # Add timeline segment to tree QtWidgets.QTreeWidgetItem(tree, [ str(sequence.name)[1:-1], # seq - str(segment.shot_name)[1:-1], # shot + str(segment.name)[1:-1], # shot CREATE_TASK_TYPE, # task type str(WORKFILE_START_FRAME), # start frame str(clip_duration), # clip duration @@ -281,6 +281,15 @@ def main_window(selection): six.reraise(tp, value, tb) return entity + def get_ftrack_entity(session, type, name, project_entity): + query = '{} where name is "{}" and project_id is "{}"'.format( + type, name, project_entity["id"]) + return session.query(query).one() + + def generate_parents_from_template(template): + template_split = template.split("/") + return template_split + with maintained_ftrack_session() as session: print("Ftrack session is: {}".format(session)) @@ -296,24 +305,45 @@ def main_window(selection): clips_info = [] for item in tree.selectedItems(): - f_entity = create_ftrack_entity( + parents = generate_parents_from_template( + hierarchy_template.text()) + print(parents) + + f_entity = get_ftrack_entity( session, "Shot", item.text(1), f_project ) + # if entity doesnt exist then create one + if not f_entity: + f_entity = create_ftrack_entity( + session, + "Shot", + item.text(1), + f_project + ) print("Shot entity is: {}".format(f_entity)) - tree_line = [ - item.text(0), - item.text(1), - item.text(2), - item.text(3), - item.text(4), - item.text(5), - item.text(6), - item.text(7) - ] + # solve handle start and end + handles = item.text(5) + if ":" in handles: + _s, _e = handles.split(":") + handles = (int(_s), int(_e)) + else: + handles = (int(handles), int(handles)) + + # populate full shot info + tree_line = { + "sequence": item.text(0), + "shot": item.text(1), + "task": item.text(2), + "frameStart": int(item.text(3)), + "duration": int(item.text(4)), + "handles": handles, + "shotDescription": item.text(6), + "taskDescription": item.text(7) + } print(tree_line) clips_info.append(tree_line) @@ -321,7 +351,7 @@ def main_window(selection): # creating ui window = QtWidgets.QWidget() - window.setMinimumSize(500, 350) + window.setMinimumSize(1500, 600) window.setWindowTitle('Sequence Shots to Ftrack') window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) window.setAttribute(QtCore.Qt.WA_DeleteOnClose) @@ -367,7 +397,14 @@ def main_window(selection): "order": 7 }, } - tree = FlameTreeWidget(columns.keys(), window) + ordered_column_labels = columns.keys() + for _name, _value in columns.items(): + ordered_column_labels.pop(_value["order"]) + ordered_column_labels.insert(_value["order"], _name) + + print(ordered_column_labels) + + tree = FlameTreeWidget(ordered_column_labels, window) # Allow multiple items in tree to be selected tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) @@ -389,16 +426,17 @@ def main_window(selection): ## Button select_all_btn = FlameButton('Select All', select_all, window) - copy_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) + ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) ## Window Layout gridbox = QtWidgets.QGridLayout() gridbox.setMargin(20) + gridbox.setHorizontalSpacing(20) gridbox.addWidget(hierarchy_label, 0, 0) - gridbox.addWidget(hierarchy_template, 0, 1) - gridbox.addWidget(tree, 1, 0, 5, 1) - gridbox.addWidget(select_all_btn, 1, 1) - gridbox.addWidget(copy_btn, 2, 1) + gridbox.addWidget(hierarchy_template, 0, 1, 1, 4) + gridbox.addWidget(tree, 1, 0, 5, 5) + gridbox.addWidget(select_all_btn, 6, 3) + gridbox.addWidget(ftrack_send_btn, 6, 4) window.setLayout(gridbox) window.show() From 2792750d253882f033317f5aebead35a3e9e08eb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Oct 2021 17:43:21 +0200 Subject: [PATCH 09/58] make it nicer --- .../flame/utility_scripts/openpype_flame_to_ftrack.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index 9364d8d57f..37c8109713 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -105,10 +105,12 @@ class FlameLabel(QtWidgets.QLabel): 'QLabel:disabled {color: #6a6a6a}') elif label_type == 'background': self.setAlignment(QtCore.Qt.AlignCenter) - self.setStyleSheet('color: #9a9a9a; background-color: #393939; font: 14px "Discreet"') + self.setStyleSheet( + 'color: #9a9a9a; background-color: #393939; font: 14px "Discreet"') elif label_type == 'outline': self.setAlignment(QtCore.Qt.AlignCenter) - self.setStyleSheet('color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"') + self.setStyleSheet( + 'color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"') class FlameLineEdit(QtWidgets.QLineEdit): @@ -192,7 +194,7 @@ def main_window(selection): def _seconds(value): if isinstance(value, str): - _zip_ft = zip((3600, 60, 1, 1/framerate), value.split(':')) + _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 @@ -450,6 +452,7 @@ def scope_sequence(selection): import flame return any(isinstance(item, flame.PySequence) for item in selection) + def get_media_panel_custom_ui_actions(): return [ { From 70928f410c6f21844fd90ce40ea9e14e3d4dd3cf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Oct 2021 23:32:49 +0200 Subject: [PATCH 10/58] adding custom attributes, tasks, comment --- .../openpype_flame_to_ftrack.py | 190 ++++++++++++++---- 1 file changed, 147 insertions(+), 43 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index 37c8109713..958d7f7a11 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -5,7 +5,7 @@ from contextlib import contextmanager # Constants WORKFILE_START_FRAME = 1001 -HIERARCHY_TEMPLATE = "shots/{sequence}" +HIERARCHY_TEMPLATE = "shots[Folder]/{sequence}[Sequence]" CREATE_TASK_TYPE = "Compositing" @@ -268,8 +268,10 @@ def main_window(selection): import flame import six import sys + import re - def create_ftrack_entity(session, type, name, parent): + def create_ftrack_entity(session, type, name, parent=None): + parent = parent or f_project entity = session.create(type, { 'name': name, 'parent': parent @@ -283,20 +285,71 @@ def main_window(selection): six.reraise(tp, value, tb) return entity - def get_ftrack_entity(session, type, name, project_entity): + def get_ftrack_entity(session, type, name, parent): query = '{} where name is "{}" and project_id is "{}"'.format( - type, name, project_entity["id"]) - return session.query(query).one() + type, name, f_project["id"]) + + try: + entity = session.query(query).one() + except Exception: + entity = None + + # if entity doesnt exist then create one + if not entity: + entity = create_ftrack_entity( + session, + type, + name, + parent + ) + + return entity def generate_parents_from_template(template): - template_split = template.split("/") - return template_split + 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(task_type, parent): + existing_task = [ + child for child in parent['children'] + if child.entity_type.lower() == 'task' + if child['name'].lower() in task_type.lower() + ] + print(existing_task) + if existing_task: + return existing_task + + # create task on shot + return session.create('Task', { + "name": task_type.lower(), + "type": task_type, + "parent": parent + }) + + # start procedure with maintained_ftrack_session() as session: print("Ftrack session is: {}".format(session)) # get project name from flame current project 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(project_name) @@ -304,52 +357,103 @@ def main_window(selection): print("Ftrack project is: {}".format(f_project)) # Get all selected items from treewidget - clips_info = [] - for item in tree.selectedItems(): - parents = generate_parents_from_template( - hierarchy_template.text()) - print(parents) - - f_entity = get_ftrack_entity( - session, - "Shot", - item.text(1), - f_project - ) - # if entity doesnt exist then create one - if not f_entity: - f_entity = create_ftrack_entity( - session, - "Shot", - item.text(1), - f_project - ) - print("Shot entity is: {}".format(f_entity)) - # solve handle start and end handles = item.text(5) if ":" in handles: _s, _e = handles.split(":") - handles = (int(_s), int(_e)) + handle_start = int(_s) + handle_end = int(_e) else: - handles = (int(handles), int(handles)) + handle_start = int(handles) + handle_end = int(handles) + + # frame ranges + frame_start = int(item.text(3)) + frame_duration = int(item.text(4)) + frame_end = frame_start + frame_duration + + # description + shot_description = item.text(6) + task_description = item.text(7) + + # other + task_type = item.text(2) + shot_name = item.text(1) + sequence_name = item.text(0) # populate full shot info - tree_line = { - "sequence": item.text(0), - "shot": item.text(1), - "task": item.text(2), - "frameStart": int(item.text(3)), - "duration": int(item.text(4)), - "handles": handles, - "shotDescription": item.text(6), - "taskDescription": item.text(7) + shot_attributes = { + "sequence": sequence_name, + "shot": shot_name, + "task": task_type } - print(tree_line) - clips_info.append(tree_line) - print("selected clips: {}".format(pformat(clips_info))) + # format hierarchy template + hierarchy_text = hierarchy_template.text() + hierarchy_text = hierarchy_text.format(**shot_attributes) + print(hierarchy_text) + + # solve parents + parents = generate_parents_from_template(hierarchy_text) + print(parents) + + # obtain shot parents entities + _parent = None + for _name, _type in parents: + p_entity = get_ftrack_entity( + session, + _type, + _name, + _parent + ) + print(p_entity) + _parent = p_entity + + # obtain shot ftrack entity + f_s_entity = get_ftrack_entity( + session, + "Shot", + item.text(1), + _parent + ) + print("Shot entity is: {}".format(f_s_entity)) + + # create custom attributtes + custom_attrs = { + "frameStart": frame_start, + "frameEnd": frame_end, + "handleStart": handle_start, + "handleEnd": handle_end + } + + # update custom attributes on shot entity + for key in custom_attrs: + f_s_entity['custom_attributes'][key] = custom_attrs[key] + + task_entity = create_task(task_type, f_s_entity) + print(task_entity) + + # Create notes. + user = session.query( + "User where username is \"{}\"".format(session.api_user) + ).first() + + print(user) + print(shot_description) + + f_s_entity.create_note(shot_description, author=user) + + if task_description: + task_entity.create_note(task_description, user) + + try: + session.commit() + except Exception: + tp, value, tb = sys.exc_info() + session.rollback() + session._configure_locations() + six.reraise(tp, value, tb) # creating ui window = QtWidgets.QWidget() From a4b2bf095143abbab0e51f768298bf149a9705aa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 30 Oct 2021 23:44:52 +0200 Subject: [PATCH 11/58] flame: fixing Task creation and adding notes --- .../publish/integrate_hierarchy_ftrack.py | 839 +++++++++++------- 1 file changed, 533 insertions(+), 306 deletions(-) diff --git a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index fbd64d9f70..25607aead6 100644 --- a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -1,356 +1,583 @@ -import sys -import collections -import six -import pyblish.api -from avalon import io +from __future__ import print_function +from PySide2 import QtWidgets, QtCore +from pprint import pformat +from contextlib import contextmanager -# Copy of constant `openpype_modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` -CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" -CUST_ATTR_GROUP = "openpype" +# Constants +WORKFILE_START_FRAME = 1001 +HIERARCHY_TEMPLATE = "shots[Folder]/{sequence}[Sequence]" +CREATE_TASK_TYPE = "Compositing" -# Copy of `get_pype_attr` from openpype_modules.ftrack.lib -# TODO import from openpype's ftrack module when possible to not break Python 2 -def get_pype_attr(session, split_hierarchical=True): - custom_attributes = [] - hier_custom_attributes = [] - # TODO remove deprecated "avalon" group from query - cust_attrs_query = ( - "select id, entity_type, object_type_id, is_hierarchical, default" - " from CustomAttributeConfiguration" - # Kept `pype` for Backwards Compatiblity - " where group.name in (\"pype\", \"{}\")" - ).format(CUST_ATTR_GROUP) - all_avalon_attr = session.query(cust_attrs_query).all() - for cust_attr in all_avalon_attr: - if split_hierarchical and cust_attr["is_hierarchical"]: - hier_custom_attributes.append(cust_attr) - continue +@contextmanager +def maintained_ftrack_session(): + import ftrack_api + import os - custom_attributes.append(cust_attr) + def validate_credentials(url, user, api): + 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 - if split_hierarchical: - # return tuple - return custom_attributes, hier_custom_attributes - - return custom_attributes - - -class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): - """ - Create entities in ftrack based on collected data from premiere - Example of entry data: - { - "ProjectXS": { - "entity_type": "Project", - "custom_attributes": { - "fps": 24,... - }, - "tasks": [ - "Compositing", - "Lighting",... *task must exist as task type in project schema* - ], - "childs": { - "sq01": { - "entity_type": "Sequence", - ... - } + try: + session = ftrack_api.Session( + server_url=url, + api_user=user, + api_key=api + ) + session.close() + 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 + print( + 'Credentials Username: "{}", API key: "{}" are valid.'.format( + user, api) + ) + return True + + # fill your own credentials + url = os.getenv("FTRACK_SERVER") + user = os.getenv("FTRACK_API_USER") + api = os.getenv("FTRACK_API_KEY") + + try: + assert validate_credentials(url, user, api), ( + "Ftrack credentials failed") + # open ftrack session + session = ftrack_api.Session( + server_url=url, + api_user=user, + api_key=api + ) + yield session + except Exception as _E: + print( + "ERROR: {}".format(_E)) + finally: + # close the session + session.close() + + +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) """ - order = pyblish.api.IntegratorOrder - 0.04 - label = 'Integrate Hierarchy To Ftrack' - families = ["shot"] - hosts = ["hiero", "resolve", "standalonepublisher"] - optional = False + def __init__(self, label_name, label_type, parent_window, *args, **kwargs): + super(FlameLabel, self).__init__(*args, **kwargs) - def process(self, context): - self.context = context - if "hierarchyContext" not in self.context.data: - return + self.setText(label_name) + self.setParent(parent_window) + self.setMinimumSize(130, 28) + self.setMaximumHeight(28) + self.setFocusPolicy(QtCore.Qt.NoFocus) - hierarchy_context = self.context.data["hierarchyContext"] + # Set label stylesheet based on label_type - self.session = self.context.data["ftrackSession"] - project_name = self.context.data["projectEntity"]["name"] - query = 'Project where full_name is "{}"'.format(project_name) - project = self.session.query(query).one() - auto_sync_state = project[ - "custom_attributes"][CUST_ATTR_AUTO_SYNC] + if label_type == 'normal': + self.setStyleSheet('QLabel {color: #9a9a9a; border-bottom: 1px inset #282828; font: 14px "Discreet"}' + 'QLabel:disabled {color: #6a6a6a}') + elif label_type == 'background': + self.setAlignment(QtCore.Qt.AlignCenter) + self.setStyleSheet( + 'color: #9a9a9a; background-color: #393939; font: 14px "Discreet"') + elif label_type == 'outline': + self.setAlignment(QtCore.Qt.AlignCenter) + self.setStyleSheet( + 'color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"') - if not io.Session: - io.install() - self.ft_project = None +class FlameLineEdit(QtWidgets.QLineEdit): + """ + Custom Qt Flame Line Edit Widget - input_data = hierarchy_context + Main window should include this: window.setFocusPolicy(QtCore.Qt.StrongFocus) - # disable termporarily ftrack project's autosyncing - if auto_sync_state: - self.auto_sync_off(project) + To use: - try: - # import ftrack hierarchy - self.import_to_ftrack(input_data) - except Exception: - raise - finally: - if auto_sync_state: - self.auto_sync_on(project) + line_edit = FlameLineEdit('Some text here', window) + """ - def import_to_ftrack(self, input_data, parent=None): - # Prequery hiearchical custom attributes - hier_custom_attributes = get_pype_attr(self.session)[1] - hier_attr_by_key = { - attr["key"]: attr - for attr in hier_custom_attributes - } - # Get ftrack api module (as they are different per python version) - ftrack_api = self.context.data["ftrackPythonModule"] + def __init__(self, text, parent_window, *args, **kwargs): + super(FlameLineEdit, self).__init__(*args, **kwargs) - for entity_name in input_data: - entity_data = input_data[entity_name] - entity_type = entity_data['entity_type'] - self.log.debug(entity_data) - self.log.debug(entity_type) + 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"}' + 'QLineEdit:focus {background-color: #474e58}' + 'QLineEdit:disabled {color: #6a6a6a; background-color: #373737}') - if entity_type.lower() == 'project': - query = 'Project where full_name is "{}"'.format(entity_name) - entity = self.session.query(query).one() - self.ft_project = entity - self.task_types = self.get_all_task_types(entity) - elif self.ft_project is None or parent is None: - raise AssertionError( - "Collected items are not in right order!" - ) +class FlameTreeWidget(QtWidgets.QTreeWidget): + """ + Custom Qt Flame Tree Widget - # try to find if entity already exists - else: - query = ( - 'TypedContext where name is "{0}" and ' - 'project_id is "{1}"' - ).format(entity_name, self.ft_project["id"]) - try: - entity = self.session.query(query).one() - except Exception: - entity = None + To use: - # Create entity if not exists - if entity is None: - entity = self.create_entity( - name=entity_name, - type=entity_type, - parent=parent - ) - # self.log.info('entity: {}'.format(dict(entity))) - # CUSTOM ATTRIBUTES - custom_attributes = entity_data.get('custom_attributes', []) - instances = [ - i for i in self.context if i.data['asset'] in entity['name'] - ] - for key in custom_attributes: - hier_attr = hier_attr_by_key.get(key) - # Use simple method if key is not hierarchical - if not hier_attr: - assert (key in entity['custom_attributes']), ( - 'Missing custom attribute key: `{0}` in attrs: ' - '`{1}`'.format(key, entity['custom_attributes'].keys()) - ) + tree_headers = ['Header1', 'Header2', 'Header3', 'Header4'] + tree = FlameTreeWidget(tree_headers, window) + """ - entity['custom_attributes'][key] = custom_attributes[key] + def __init__(self, tree_headers, parent_window, *args, **kwargs): + super(FlameTreeWidget, self).__init__(*args, **kwargs) - else: - # Use ftrack operations method to set hiearchical - # attribute value. - # - this is because there may be non hiearchical custom - # attributes with different properties - entity_key = collections.OrderedDict() - entity_key["configuration_id"] = hier_attr["id"] - entity_key["entity_id"] = entity["id"] - self.session.recorded_operations.push( - ftrack_api.operation.UpdateEntityOperation( - "ContextCustomAttributeValue", - entity_key, - "value", - ftrack_api.symbol.NOT_SET, - custom_attributes[key] + 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"}' + 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' + 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' + 'QTreeWidget::item:selected {selection-background-color: #111111}' + 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' + '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"}' + 'QPushButton:pressed {color: #d9d9d9; background-color: #4f4f4f; border-top: 1px inset #666666; font: italic}' + 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') + + +def main_window(selection): + 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 timecode_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(timecode_to_frames(timecode, start='00:00:00:00'))) + + return frames + + def timeline_info(selection): + import flame + + # identificar as informacoes dos segmentos na timeline + for sequence in selection: + frame_rate = float(str(sequence.frame_rate)[:-4]) + for ver in sequence.versions: + for tracks in ver.tracks: + for segment in tracks.segments: + print(segment.type) + # get clip frame duration + record_duration = str(segment.record_duration)[1:-1] + clip_duration = 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(tree, [ + str(sequence.name)[1:-1], # seq + str(segment.name)[1:-1], # shot + CREATE_TASK_TYPE, # task type + str(WORKFILE_START_FRAME), # start frame + str(clip_duration), # clip duration + "0:0", # handles + shot_description, # shot description + str(segment.comment)[1:-1] # task description + ]).setFlags( + QtCore.Qt.ItemIsEditable + | QtCore.Qt.ItemIsEnabled + | QtCore.Qt.ItemIsSelectable ) - ) - for instance in instances: - instance.data['ftrackEntity'] = entity + # Select top item in tree + tree.setCurrentItem(tree.topLevelItem(0)) + + def select_all(): + + tree.selectAll() + + def send_to_ftrack(): + import flame + import six + import sys + import re + + def create_ftrack_entity(session, type, name, parent=None): + parent = parent or f_project + 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(session, type, name, parent): + query = '{} where name is "{}" and project_id is "{}"'.format( + type, name, f_project["id"]) + + try: + entity = session.query(query).one() + except Exception: + entity = None + + # if entity doesnt exist then create one + if not entity: + entity = create_ftrack_entity( + session, + type, + name, + parent + ) + + return entity + + def generate_parents_from_template(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 get_all_task_types(): + tasks = {} + proj_template = f_project['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 + + def create_task(task_type, parent): + existing_task = [ + child for child in parent['children'] + if child.entity_type.lower() == 'task' + if child['name'].lower() in task_type.lower() + ] + + if existing_task: + return existing_task + + task = session.create('Task', { + "name": task_type.lower(), + "parent": parent + }) + task_types = get_all_task_types() + task["type"] = task_types[task_type] + + return task + + + # start procedure + with maintained_ftrack_session() as session: + print("Ftrack session is: {}".format(session)) + + # get project name from flame current project + 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(project_name) + f_project = session.query(query).one() + print("Ftrack project is: {}".format(f_project)) + + # Get all selected items from treewidget + for item in tree.selectedItems(): + # solve handle start and end + handles = item.text(5) + if ":" in handles: + _s, _e = handles.split(":") + handle_start = int(_s) + handle_end = int(_e) + else: + handle_start = int(handles) + handle_end = int(handles) + + # frame ranges + frame_start = int(item.text(3)) + frame_duration = int(item.text(4)) + frame_end = frame_start + frame_duration + + # description + shot_description = item.text(6) + task_description = item.text(7) + + # other + task_type = item.text(2) + shot_name = item.text(1) + sequence_name = item.text(0) + + # populate full shot info + shot_attributes = { + "sequence": sequence_name, + "shot": shot_name, + "task": task_type + } + + # format hierarchy template + hierarchy_text = hierarchy_template.text() + hierarchy_text = hierarchy_text.format(**shot_attributes) + print(hierarchy_text) + + # solve parents + parents = generate_parents_from_template(hierarchy_text) + print(parents) + + # obtain shot parents entities + _parent = None + for _name, _type in parents: + p_entity = get_ftrack_entity( + session, + _type, + _name, + _parent + ) + print(p_entity) + _parent = p_entity + + # obtain shot ftrack entity + f_s_entity = get_ftrack_entity( + session, + "Shot", + item.text(1), + _parent + ) + print("Shot entity is: {}".format(f_s_entity)) + + # create custom attributtes + custom_attrs = { + "frameStart": frame_start, + "frameEnd": frame_end, + "handleStart": handle_start, + "handleEnd": handle_end + } + + # update custom attributes on shot entity + for key in custom_attrs: + f_s_entity['custom_attributes'][key] = custom_attrs[key] + + task_entity = create_task(task_type, f_s_entity) + + # Create notes. + user = session.query( + "User where username is \"{}\"".format(session.api_user) + ).first() + + f_s_entity.create_note(shot_description, author=user) + + if task_description: + task_entity.create_note(task_description, user) try: - self.session.commit() + session.commit() except Exception: tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() + session.rollback() + session._configure_locations() six.reraise(tp, value, tb) - # TASKS - tasks = entity_data.get('tasks', []) - existing_tasks = [] - tasks_to_create = [] - for child in entity['children']: - if child.entity_type.lower() == 'task': - existing_tasks.append(child['name'].lower()) - # existing_tasks.append(child['type']['name']) + # creating ui + window = QtWidgets.QWidget() + window.setMinimumSize(1500, 600) + window.setWindowTitle('Sequence Shots to Ftrack') + window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + window.setAttribute(QtCore.Qt.WA_DeleteOnClose) + window.setStyleSheet('background-color: #313131') - for task_name in tasks: - task_type = tasks[task_name]["type"] - if task_name.lower() in existing_tasks: - print("Task {} already exists".format(task_name)) - continue - tasks_to_create.append((task_name, task_type)) + # Center window in linux + resolution = QtWidgets.QDesktopWidget().screenGeometry() + window.move((resolution.width() / 2) - (window.frameSize().width() / 2), + (resolution.height() / 2) - (window.frameSize().height() / 2)) - for task_name, task_type in tasks_to_create: - self.create_task( - name=task_name, - task_type=task_type, - parent=entity - ) - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) + # TreeWidget + columns = { + "Sequence name": { + "columnWidth": 100, + "order": 0 + }, + "Shot name": { + "columnWidth": 100, + "order": 1 + }, + "Task type": { + "columnWidth": 100, + "order": 2 + }, + "Start frame": { + "columnWidth": 100, + "order": 3 + }, + "Clip duration": { + "columnWidth": 100, + "order": 4 + }, + "Handles": { + "columnWidth": 100, + "order": 5 + }, + "Shot description": { + "columnWidth": 300, + "order": 6 + }, + "Task description": { + "columnWidth": 300, + "order": 7 + }, + } + ordered_column_labels = columns.keys() + for _name, _value in columns.items(): + ordered_column_labels.pop(_value["order"]) + ordered_column_labels.insert(_value["order"], _name) - # Incoming links. - self.create_links(entity_data, entity) - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) + print(ordered_column_labels) - # Create notes. - user = self.session.query( - "User where username is \"{}\"".format(self.session.api_user) - ).first() - if user: - for comment in entity_data.get("comments", []): - entity.create_note(comment, user) - else: - self.log.warning( - "Was not able to query current User {}".format( - self.session.api_user - ) - ) - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) + tree = FlameTreeWidget(ordered_column_labels, window) - # Import children. - if 'childs' in entity_data: - self.import_to_ftrack( - entity_data['childs'], entity) + # Allow multiple items in tree to be selected + tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) - def create_links(self, entity_data, entity): - # Clear existing links. - for link in entity.get("incoming_links", []): - self.session.delete(link) - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) + # Set tree column width + for _name, _val in columns.items(): + tree.setColumnWidth( + _val["order"], + _val["columnWidth"] + ) - # Create new links. - for input in entity_data.get("inputs", []): - input_id = io.find_one({"_id": input})["data"]["ftrackId"] - assetbuild = self.session.get("AssetBuild", input_id) - self.log.debug( - "Creating link from {0} to {1}".format( - assetbuild["name"], entity["name"] - ) - ) - self.session.create( - "TypedContextLink", {"from": assetbuild, "to": entity} - ) + # Prevent weird characters when shrinking tree columns + tree.setTextElideMode(QtCore.Qt.ElideNone) - def get_all_task_types(self, project): - tasks = {} - proj_template = project['project_schema'] - temp_task_types = proj_template['_task_type_schema']['types'] + # input fields + hierarchy_label = FlameLabel( + 'Parents template', 'normal', window) + hierarchy_template = FlameLineEdit(HIERARCHY_TEMPLATE, window) - for type in temp_task_types: - if type['name'] not in tasks: - tasks[type['name']] = type + ## Button + select_all_btn = FlameButton('Select All', select_all, window) + ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) - return tasks + ## Window Layout + gridbox = QtWidgets.QGridLayout() + gridbox.setMargin(20) + gridbox.setHorizontalSpacing(20) + gridbox.addWidget(hierarchy_label, 0, 0) + gridbox.addWidget(hierarchy_template, 0, 1, 1, 4) + gridbox.addWidget(tree, 1, 0, 5, 5) + gridbox.addWidget(select_all_btn, 6, 3) + gridbox.addWidget(ftrack_send_btn, 6, 4) - def create_task(self, name, task_type, parent): - task = self.session.create('Task', { - 'name': name, - 'parent': parent - }) - # TODO not secured!!! - check if task_type exists - self.log.info(task_type) - self.log.info(self.task_types) - task['type'] = self.task_types[task_type] + window.setLayout(gridbox) + window.show() - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) + timeline_info(selection) - return task + return window - def create_entity(self, name, type, parent): - entity = self.session.create(type, { - 'name': name, - 'parent': parent - }) - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) - return entity +def scope_sequence(selection): + import flame + return any(isinstance(item, flame.PySequence) for item in selection) - def auto_sync_off(self, project): - project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False - self.log.info("Ftrack autosync swithed off") +def get_media_panel_custom_ui_actions(): + return [ + { + "name": "OpenPype: Ftrack", + "actions": [ + { + "name": "Create Shots", + "isVisible": scope_sequence, + "execute": main_window + } + ] + } - 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 auto_sync_on(self, project): - - project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True - - self.log.info("Ftrack autosync swithed on") - - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() - six.reraise(tp, value, tb) + ] From af1c979fa16dd176f1dbc8118979f36800f44334 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 1 Nov 2021 13:55:01 +0100 Subject: [PATCH 12/58] adding export presets --- .../openpype_seg_thumbnails_jpg.xml | 58 +++++++++++++++ .../export_preset/openpype_seg_video_h264.xml | 72 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_thumbnails_jpg.xml create mode 100644 openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_video_h264.xml diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_thumbnails_jpg.xml b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_thumbnails_jpg.xml new file mode 100644 index 0000000000..fa43ceece7 --- /dev/null +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_thumbnails_jpg.xml @@ -0,0 +1,58 @@ + + + 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/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_video_h264.xml b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_video_h264.xml new file mode 100644 index 0000000000..64447b76e8 --- /dev/null +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_video_h264.xml @@ -0,0 +1,72 @@ + + + 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 + <segment name>_<video codec> + 0 + PCS_709 + None + Autodesk + Flame + 2021 + + + + 4 + 1 + 2 + + \ No newline at end of file From 0c252055e88b8b44fb1c5629e31b78562b681ce0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 1 Nov 2021 14:02:28 +0100 Subject: [PATCH 13/58] fix module code content swap by mistake --- .../openpype_flame_to_ftrack.py | 46 +- .../publish/integrate_hierarchy_ftrack.py | 855 +++++++----------- 2 files changed, 348 insertions(+), 553 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py index 958d7f7a11..ba57023edc 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py @@ -3,11 +3,17 @@ from PySide2 import QtWidgets, QtCore from pprint import pformat from contextlib import contextmanager + # Constants WORKFILE_START_FRAME = 1001 HIERARCHY_TEMPLATE = "shots[Folder]/{sequence}[Sequence]" CREATE_TASK_TYPE = "Compositing" +# Fill following constants or set them via environment variable +FTRACK_API_KEY = None +FTRACK_API_USER = None +FTRACK_SERVER = None + @contextmanager def maintained_ftrack_session(): @@ -56,9 +62,9 @@ def maintained_ftrack_session(): return True # fill your own credentials - url = os.getenv("FTRACK_SERVER") - user = os.getenv("FTRACK_API_USER") - api = os.getenv("FTRACK_API_KEY") + 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 "" try: assert validate_credentials(url, user, api), ( @@ -326,22 +332,35 @@ def main_window(selection): return parents + def get_all_task_types(): + tasks = {} + proj_template = f_project['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 + def create_task(task_type, parent): existing_task = [ child for child in parent['children'] if child.entity_type.lower() == 'task' if child['name'].lower() in task_type.lower() ] - print(existing_task) + if existing_task: return existing_task - # create task on shot - return session.create('Task', { + task = session.create('Task', { "name": task_type.lower(), - "type": task_type, "parent": parent }) + task_types = get_all_task_types() + task["type"] = task_types[task_type] + + return task # start procedure with maintained_ftrack_session() as session: @@ -432,16 +451,12 @@ def main_window(selection): f_s_entity['custom_attributes'][key] = custom_attrs[key] task_entity = create_task(task_type, f_s_entity) - print(task_entity) # Create notes. user = session.query( "User where username is \"{}\"".format(session.api_user) ).first() - print(user) - print(shot_description) - f_s_entity.create_note(shot_description, author=user) if task_description: @@ -530,6 +545,11 @@ def main_window(selection): 'Parents template', 'normal', window) hierarchy_template = FlameLineEdit(HIERARCHY_TEMPLATE, window) + # input fields + start_frame_label = FlameLabel( + 'Workfile start frame', 'normal', window) + start_frame = FlameLineEdit(WORKFILE_START_FRAME, window) + ## Button select_all_btn = FlameButton('Select All', select_all, window) ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) @@ -539,7 +559,9 @@ def main_window(selection): gridbox.setMargin(20) gridbox.setHorizontalSpacing(20) gridbox.addWidget(hierarchy_label, 0, 0) - gridbox.addWidget(hierarchy_template, 0, 1, 1, 4) + gridbox.addWidget(hierarchy_template, 0, 1, 1, 2) + gridbox.addWidget(start_frame_label, 0, 3) + gridbox.addWidget(start_frame, 0, 3, 1, 1) gridbox.addWidget(tree, 1, 0, 5, 5) gridbox.addWidget(select_all_btn, 6, 3) gridbox.addWidget(ftrack_send_btn, 6, 4) diff --git a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 25607aead6..fbd64d9f70 100644 --- a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -1,583 +1,356 @@ -from __future__ import print_function -from PySide2 import QtWidgets, QtCore -from pprint import pformat -from contextlib import contextmanager +import sys +import collections +import six +import pyblish.api +from avalon import io -# Constants -WORKFILE_START_FRAME = 1001 -HIERARCHY_TEMPLATE = "shots[Folder]/{sequence}[Sequence]" -CREATE_TASK_TYPE = "Compositing" +# Copy of constant `openpype_modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` +CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" +CUST_ATTR_GROUP = "openpype" -@contextmanager -def maintained_ftrack_session(): - import ftrack_api - import os +# Copy of `get_pype_attr` from openpype_modules.ftrack.lib +# TODO import from openpype's ftrack module when possible to not break Python 2 +def get_pype_attr(session, split_hierarchical=True): + custom_attributes = [] + hier_custom_attributes = [] + # TODO remove deprecated "avalon" group from query + cust_attrs_query = ( + "select id, entity_type, object_type_id, is_hierarchical, default" + " from CustomAttributeConfiguration" + # Kept `pype` for Backwards Compatiblity + " where group.name in (\"pype\", \"{}\")" + ).format(CUST_ATTR_GROUP) + all_avalon_attr = session.query(cust_attrs_query).all() + for cust_attr in all_avalon_attr: + if split_hierarchical and cust_attr["is_hierarchical"]: + hier_custom_attributes.append(cust_attr) + continue - def validate_credentials(url, user, api): - 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 + custom_attributes.append(cust_attr) + + if split_hierarchical: + # return tuple + return custom_attributes, hier_custom_attributes + + return custom_attributes + + +class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): + """ + Create entities in ftrack based on collected data from premiere + Example of entry data: + { + "ProjectXS": { + "entity_type": "Project", + "custom_attributes": { + "fps": 24,... + }, + "tasks": [ + "Compositing", + "Lighting",... *task must exist as task type in project schema* + ], + "childs": { + "sq01": { + "entity_type": "Sequence", + ... + } + } + } + } + """ + + order = pyblish.api.IntegratorOrder - 0.04 + label = 'Integrate Hierarchy To Ftrack' + families = ["shot"] + hosts = ["hiero", "resolve", "standalonepublisher"] + optional = False + + def process(self, context): + self.context = context + if "hierarchyContext" not in self.context.data: + return + + hierarchy_context = self.context.data["hierarchyContext"] + + self.session = self.context.data["ftrackSession"] + project_name = self.context.data["projectEntity"]["name"] + query = 'Project where full_name is "{}"'.format(project_name) + project = self.session.query(query).one() + auto_sync_state = project[ + "custom_attributes"][CUST_ATTR_AUTO_SYNC] + + if not io.Session: + io.install() + + self.ft_project = None + + input_data = hierarchy_context + + # disable termporarily ftrack project's autosyncing + if auto_sync_state: + self.auto_sync_off(project) try: - session = ftrack_api.Session( - server_url=url, - api_user=user, - api_key=api - ) - session.close() - 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), - } + # import ftrack hierarchy + self.import_to_ftrack(input_data) + except Exception: + raise + finally: + if auto_sync_state: + self.auto_sync_on(project) - 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 - print( - 'Credentials Username: "{}", API key: "{}" are valid.'.format( - user, api) - ) - return True + def import_to_ftrack(self, input_data, parent=None): + # Prequery hiearchical custom attributes + hier_custom_attributes = get_pype_attr(self.session)[1] + hier_attr_by_key = { + attr["key"]: attr + for attr in hier_custom_attributes + } + # Get ftrack api module (as they are different per python version) + ftrack_api = self.context.data["ftrackPythonModule"] - # fill your own credentials - url = os.getenv("FTRACK_SERVER") - user = os.getenv("FTRACK_API_USER") - api = os.getenv("FTRACK_API_KEY") + for entity_name in input_data: + entity_data = input_data[entity_name] + entity_type = entity_data['entity_type'] + self.log.debug(entity_data) + self.log.debug(entity_type) - try: - assert validate_credentials(url, user, api), ( - "Ftrack credentials failed") - # open ftrack session - session = ftrack_api.Session( - server_url=url, - api_user=user, - api_key=api - ) - yield session - except Exception as _E: - print( - "ERROR: {}".format(_E)) - finally: - # close the session - session.close() + if entity_type.lower() == 'project': + query = 'Project where full_name is "{}"'.format(entity_name) + entity = self.session.query(query).one() + self.ft_project = entity + self.task_types = self.get_all_task_types(entity) - -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"}' - 'QLabel:disabled {color: #6a6a6a}') - elif label_type == 'background': - self.setAlignment(QtCore.Qt.AlignCenter) - self.setStyleSheet( - 'color: #9a9a9a; background-color: #393939; font: 14px "Discreet"') - elif label_type == 'outline': - self.setAlignment(QtCore.Qt.AlignCenter) - self.setStyleSheet( - 'color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"') - - -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"}' - 'QLineEdit:focus {background-color: #474e58}' - '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"}' - 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' - 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' - 'QTreeWidget::item:selected {selection-background-color: #111111}' - 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' - '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"}' - 'QPushButton:pressed {color: #d9d9d9; background-color: #4f4f4f; border-top: 1px inset #666666; font: italic}' - 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') - - -def main_window(selection): - 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 timecode_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(timecode_to_frames(timecode, start='00:00:00:00'))) - - return frames - - def timeline_info(selection): - import flame - - # identificar as informacoes dos segmentos na timeline - for sequence in selection: - frame_rate = float(str(sequence.frame_rate)[:-4]) - for ver in sequence.versions: - for tracks in ver.tracks: - for segment in tracks.segments: - print(segment.type) - # get clip frame duration - record_duration = str(segment.record_duration)[1:-1] - clip_duration = 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(tree, [ - str(sequence.name)[1:-1], # seq - str(segment.name)[1:-1], # shot - CREATE_TASK_TYPE, # task type - str(WORKFILE_START_FRAME), # start frame - str(clip_duration), # clip duration - "0:0", # handles - shot_description, # shot description - str(segment.comment)[1:-1] # task description - ]).setFlags( - QtCore.Qt.ItemIsEditable - | QtCore.Qt.ItemIsEnabled - | QtCore.Qt.ItemIsSelectable - ) - - # Select top item in tree - tree.setCurrentItem(tree.topLevelItem(0)) - - def select_all(): - - tree.selectAll() - - def send_to_ftrack(): - import flame - import six - import sys - import re - - def create_ftrack_entity(session, type, name, parent=None): - parent = parent or f_project - 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(session, type, name, parent): - query = '{} where name is "{}" and project_id is "{}"'.format( - type, name, f_project["id"]) - - try: - entity = session.query(query).one() - except Exception: - entity = None - - # if entity doesnt exist then create one - if not entity: - entity = create_ftrack_entity( - session, - type, - name, - parent + elif self.ft_project is None or parent is None: + raise AssertionError( + "Collected items are not in right order!" ) - return entity + # try to find if entity already exists + else: + query = ( + 'TypedContext where name is "{0}" and ' + 'project_id is "{1}"' + ).format(entity_name, self.ft_project["id"]) + try: + entity = self.session.query(query).one() + except Exception: + entity = None - def generate_parents_from_template(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 get_all_task_types(): - tasks = {} - proj_template = f_project['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 - - def create_task(task_type, parent): - existing_task = [ - child for child in parent['children'] - if child.entity_type.lower() == 'task' - if child['name'].lower() in task_type.lower() + # Create entity if not exists + if entity is None: + entity = self.create_entity( + name=entity_name, + type=entity_type, + parent=parent + ) + # self.log.info('entity: {}'.format(dict(entity))) + # CUSTOM ATTRIBUTES + custom_attributes = entity_data.get('custom_attributes', []) + instances = [ + i for i in self.context if i.data['asset'] in entity['name'] ] - - if existing_task: - return existing_task - - task = session.create('Task', { - "name": task_type.lower(), - "parent": parent - }) - task_types = get_all_task_types() - task["type"] = task_types[task_type] - - return task - - - # start procedure - with maintained_ftrack_session() as session: - print("Ftrack session is: {}".format(session)) - - # get project name from flame current project - 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(project_name) - f_project = session.query(query).one() - print("Ftrack project is: {}".format(f_project)) - - # Get all selected items from treewidget - for item in tree.selectedItems(): - # solve handle start and end - handles = item.text(5) - if ":" in handles: - _s, _e = handles.split(":") - handle_start = int(_s) - handle_end = int(_e) - else: - handle_start = int(handles) - handle_end = int(handles) - - # frame ranges - frame_start = int(item.text(3)) - frame_duration = int(item.text(4)) - frame_end = frame_start + frame_duration - - # description - shot_description = item.text(6) - task_description = item.text(7) - - # other - task_type = item.text(2) - shot_name = item.text(1) - sequence_name = item.text(0) - - # populate full shot info - shot_attributes = { - "sequence": sequence_name, - "shot": shot_name, - "task": task_type - } - - # format hierarchy template - hierarchy_text = hierarchy_template.text() - hierarchy_text = hierarchy_text.format(**shot_attributes) - print(hierarchy_text) - - # solve parents - parents = generate_parents_from_template(hierarchy_text) - print(parents) - - # obtain shot parents entities - _parent = None - for _name, _type in parents: - p_entity = get_ftrack_entity( - session, - _type, - _name, - _parent + for key in custom_attributes: + hier_attr = hier_attr_by_key.get(key) + # Use simple method if key is not hierarchical + if not hier_attr: + assert (key in entity['custom_attributes']), ( + 'Missing custom attribute key: `{0}` in attrs: ' + '`{1}`'.format(key, entity['custom_attributes'].keys()) ) - print(p_entity) - _parent = p_entity - # obtain shot ftrack entity - f_s_entity = get_ftrack_entity( - session, - "Shot", - item.text(1), - _parent - ) - print("Shot entity is: {}".format(f_s_entity)) + entity['custom_attributes'][key] = custom_attributes[key] - # create custom attributtes - custom_attrs = { - "frameStart": frame_start, - "frameEnd": frame_end, - "handleStart": handle_start, - "handleEnd": handle_end - } + else: + # Use ftrack operations method to set hiearchical + # attribute value. + # - this is because there may be non hiearchical custom + # attributes with different properties + entity_key = collections.OrderedDict() + entity_key["configuration_id"] = hier_attr["id"] + entity_key["entity_id"] = entity["id"] + self.session.recorded_operations.push( + ftrack_api.operation.UpdateEntityOperation( + "ContextCustomAttributeValue", + entity_key, + "value", + ftrack_api.symbol.NOT_SET, + custom_attributes[key] + ) + ) - # update custom attributes on shot entity - for key in custom_attrs: - f_s_entity['custom_attributes'][key] = custom_attrs[key] - - task_entity = create_task(task_type, f_s_entity) - - # Create notes. - user = session.query( - "User where username is \"{}\"".format(session.api_user) - ).first() - - f_s_entity.create_note(shot_description, author=user) - - if task_description: - task_entity.create_note(task_description, user) + for instance in instances: + instance.data['ftrackEntity'] = entity try: - session.commit() + self.session.commit() except Exception: tp, value, tb = sys.exc_info() - session.rollback() - session._configure_locations() + self.session.rollback() + self.session._configure_locations() six.reraise(tp, value, tb) - # creating ui - window = QtWidgets.QWidget() - window.setMinimumSize(1500, 600) - window.setWindowTitle('Sequence Shots to Ftrack') - window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - window.setAttribute(QtCore.Qt.WA_DeleteOnClose) - window.setStyleSheet('background-color: #313131') + # TASKS + tasks = entity_data.get('tasks', []) + existing_tasks = [] + tasks_to_create = [] + for child in entity['children']: + if child.entity_type.lower() == 'task': + existing_tasks.append(child['name'].lower()) + # existing_tasks.append(child['type']['name']) - # Center window in linux - resolution = QtWidgets.QDesktopWidget().screenGeometry() - window.move((resolution.width() / 2) - (window.frameSize().width() / 2), - (resolution.height() / 2) - (window.frameSize().height() / 2)) + for task_name in tasks: + task_type = tasks[task_name]["type"] + if task_name.lower() in existing_tasks: + print("Task {} already exists".format(task_name)) + continue + tasks_to_create.append((task_name, task_type)) - # TreeWidget - columns = { - "Sequence name": { - "columnWidth": 100, - "order": 0 - }, - "Shot name": { - "columnWidth": 100, - "order": 1 - }, - "Task type": { - "columnWidth": 100, - "order": 2 - }, - "Start frame": { - "columnWidth": 100, - "order": 3 - }, - "Clip duration": { - "columnWidth": 100, - "order": 4 - }, - "Handles": { - "columnWidth": 100, - "order": 5 - }, - "Shot description": { - "columnWidth": 300, - "order": 6 - }, - "Task description": { - "columnWidth": 300, - "order": 7 - }, - } - ordered_column_labels = columns.keys() - for _name, _value in columns.items(): - ordered_column_labels.pop(_value["order"]) - ordered_column_labels.insert(_value["order"], _name) + for task_name, task_type in tasks_to_create: + self.create_task( + name=task_name, + task_type=task_type, + parent=entity + ) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) - print(ordered_column_labels) + # Incoming links. + self.create_links(entity_data, entity) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) - tree = FlameTreeWidget(ordered_column_labels, window) + # Create notes. + user = self.session.query( + "User where username is \"{}\"".format(self.session.api_user) + ).first() + if user: + for comment in entity_data.get("comments", []): + entity.create_note(comment, user) + else: + self.log.warning( + "Was not able to query current User {}".format( + self.session.api_user + ) + ) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) - # Allow multiple items in tree to be selected - tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + # Import children. + if 'childs' in entity_data: + self.import_to_ftrack( + entity_data['childs'], entity) - # Set tree column width - for _name, _val in columns.items(): - tree.setColumnWidth( - _val["order"], - _val["columnWidth"] - ) + def create_links(self, entity_data, entity): + # Clear existing links. + for link in entity.get("incoming_links", []): + self.session.delete(link) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) - # Prevent weird characters when shrinking tree columns - tree.setTextElideMode(QtCore.Qt.ElideNone) + # Create new links. + for input in entity_data.get("inputs", []): + input_id = io.find_one({"_id": input})["data"]["ftrackId"] + assetbuild = self.session.get("AssetBuild", input_id) + self.log.debug( + "Creating link from {0} to {1}".format( + assetbuild["name"], entity["name"] + ) + ) + self.session.create( + "TypedContextLink", {"from": assetbuild, "to": entity} + ) - # input fields - hierarchy_label = FlameLabel( - 'Parents template', 'normal', window) - hierarchy_template = FlameLineEdit(HIERARCHY_TEMPLATE, window) + def get_all_task_types(self, project): + tasks = {} + proj_template = project['project_schema'] + temp_task_types = proj_template['_task_type_schema']['types'] - ## Button - select_all_btn = FlameButton('Select All', select_all, window) - ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) + for type in temp_task_types: + if type['name'] not in tasks: + tasks[type['name']] = type - ## Window Layout - gridbox = QtWidgets.QGridLayout() - gridbox.setMargin(20) - gridbox.setHorizontalSpacing(20) - gridbox.addWidget(hierarchy_label, 0, 0) - gridbox.addWidget(hierarchy_template, 0, 1, 1, 4) - gridbox.addWidget(tree, 1, 0, 5, 5) - gridbox.addWidget(select_all_btn, 6, 3) - gridbox.addWidget(ftrack_send_btn, 6, 4) + return tasks - window.setLayout(gridbox) - window.show() + def create_task(self, name, task_type, parent): + task = self.session.create('Task', { + 'name': name, + 'parent': parent + }) + # TODO not secured!!! - check if task_type exists + self.log.info(task_type) + self.log.info(self.task_types) + task['type'] = self.task_types[task_type] - timeline_info(selection) + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) - return window + return task + def create_entity(self, name, type, parent): + entity = self.session.create(type, { + 'name': name, + 'parent': parent + }) + 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 scope_sequence(selection): - import flame - return any(isinstance(item, flame.PySequence) for item in selection) + return entity + def auto_sync_off(self, project): + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False -def get_media_panel_custom_ui_actions(): - return [ - { - "name": "OpenPype: Ftrack", - "actions": [ - { - "name": "Create Shots", - "isVisible": scope_sequence, - "execute": main_window - } - ] - } + self.log.info("Ftrack autosync swithed off") - ] + 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 auto_sync_on(self, project): + + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True + + self.log.info("Ftrack autosync swithed on") + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + self.session._configure_locations() + six.reraise(tp, value, tb) From bb0a323be6f789d233a71cb07db257ee4e4649b6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 1 Nov 2021 14:03:42 +0100 Subject: [PATCH 14/58] add module file into folder --- .../{ => openpype_flame_to_ftrack}/openpype_flame_to_ftrack.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/flame/utility_scripts/{ => openpype_flame_to_ftrack}/openpype_flame_to_ftrack.py (100%) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py similarity index 100% rename from openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack.py rename to openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py From 4c574478bd3bc55366276fc381a6c253ac2e38fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 1 Nov 2021 17:03:41 +0100 Subject: [PATCH 15/58] updating layout and adding global input fields --- .../openpype_flame_to_ftrack.py | 177 +++++++++++------- 1 file changed, 111 insertions(+), 66 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index ba57023edc..402f3918c3 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -1,4 +1,5 @@ from __future__ import print_function +import os from PySide2 import QtWidgets, QtCore from pprint import pformat from contextlib import contextmanager @@ -14,6 +15,9 @@ FTRACK_API_KEY = None FTRACK_API_USER = None FTRACK_SERVER = None +SCRIPT_DIR = os.path.dirname(__file__) +EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") + @contextmanager def maintained_ftrack_session(): @@ -84,6 +88,40 @@ def maintained_ftrack_session(): session.close() +@contextmanager +def make_temp_dir(): + import tempfile + import shutil + + try: + dirpath = tempfile.mkdtemp() + + yield dirpath + + except IOError as _error: + raise IOError( + "Not able to create temp dir file: {}".format( + _error + ) + ) + + finally: + print(dirpath) + # shutil.rmtree(dirpath) + + +def get_all_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 FlameLabel(QtWidgets.QLabel): """ Custom Qt Flame Label Widget @@ -251,10 +289,7 @@ def main_window(selection): QtWidgets.QTreeWidgetItem(tree, [ str(sequence.name)[1:-1], # seq str(segment.name)[1:-1], # shot - CREATE_TASK_TYPE, # task type - str(WORKFILE_START_FRAME), # start frame str(clip_duration), # clip duration - "0:0", # handles shot_description, # shot description str(segment.comment)[1:-1] # task description ]).setFlags( @@ -332,17 +367,6 @@ def main_window(selection): return parents - def get_all_task_types(): - tasks = {} - proj_template = f_project['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 - def create_task(task_type, parent): existing_task = [ child for child in parent['children'] @@ -357,13 +381,23 @@ def main_window(selection): "name": task_type.lower(), "parent": parent }) - task_types = get_all_task_types() + task_types = get_all_task_types(f_project) task["type"] = task_types[task_type] return task + def export_thumbnail(sequence, tempdir_path): + export_preset = os.path.join( + EXPORT_PRESETS_DIR, + "openpype_seg_thumbnails_jpg.xml" + ) + poster_frame_exporter = flame.PyExporter() + poster_frame_exporter.foreground = True + poster_frame_exporter.export(sequence, export_preset, tempdir_path) + # start procedure - with maintained_ftrack_session() as session: + with maintained_ftrack_session() as session, make_temp_dir() as tempdir_path: + print("tempdir_path: {}".format(tempdir_path)) print("Ftrack session is: {}".format(session)) # get project name from flame current project @@ -375,31 +409,30 @@ def main_window(selection): f_project = session.query(query).one() print("Ftrack project is: {}".format(f_project)) + # get hanldes from gui input + handles = handles_input.text() + handle_start = int(handles) + handle_end = int(handles) + + # get frame start from gui input + frame_start = int(start_frame_input.text()) + + # get task type from gui input + task_type = task_type_input.text() + # Get all selected items from treewidget for item in tree.selectedItems(): - # solve handle start and end - handles = item.text(5) - if ":" in handles: - _s, _e = handles.split(":") - handle_start = int(_s) - handle_end = int(_e) - else: - handle_start = int(handles) - handle_end = int(handles) - # frame ranges - frame_start = int(item.text(3)) - frame_duration = int(item.text(4)) + frame_duration = int(item.text(2)) frame_end = frame_start + frame_duration # description - shot_description = item.text(6) - task_description = item.text(7) + shot_description = item.text(3) + task_description = item.text(4) # other - task_type = item.text(2) - shot_name = item.text(1) sequence_name = item.text(0) + shot_name = item.text(1) # populate full shot info shot_attributes = { @@ -409,7 +442,7 @@ def main_window(selection): } # format hierarchy template - hierarchy_text = hierarchy_template.text() + hierarchy_text = hierarchy_template_input.text() hierarchy_text = hierarchy_text.format(**shot_attributes) print(hierarchy_text) @@ -486,36 +519,24 @@ def main_window(selection): # TreeWidget columns = { "Sequence name": { - "columnWidth": 100, + "columnWidth": 200, "order": 0 }, "Shot name": { - "columnWidth": 100, + "columnWidth": 200, "order": 1 }, - "Task type": { - "columnWidth": 100, - "order": 2 - }, - "Start frame": { - "columnWidth": 100, - "order": 3 - }, "Clip duration": { "columnWidth": 100, - "order": 4 - }, - "Handles": { - "columnWidth": 100, - "order": 5 + "order": 2 }, "Shot description": { - "columnWidth": 300, - "order": 6 + "columnWidth": 500, + "order": 3 }, "Task description": { - "columnWidth": 300, - "order": 7 + "columnWidth": 500, + "order": 4 }, } ordered_column_labels = columns.keys() @@ -543,30 +564,54 @@ def main_window(selection): # input fields hierarchy_label = FlameLabel( 'Parents template', 'normal', window) - hierarchy_template = FlameLineEdit(HIERARCHY_TEMPLATE, window) + hierarchy_template_input = FlameLineEdit(HIERARCHY_TEMPLATE, window) - # input fields start_frame_label = FlameLabel( 'Workfile start frame', 'normal', window) - start_frame = FlameLineEdit(WORKFILE_START_FRAME, window) + start_frame_input = FlameLineEdit(str(WORKFILE_START_FRAME), window) + + handles_label = FlameLabel( + 'Shot handles', 'normal', window) + handles_input = FlameLineEdit(str(5), window) + + task_type_label = FlameLabel( + 'Create Task (type)', 'normal', window) + task_type_input = FlameLineEdit(CREATE_TASK_TYPE, window) ## Button select_all_btn = FlameButton('Select All', select_all, window) ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) ## Window Layout - gridbox = QtWidgets.QGridLayout() - gridbox.setMargin(20) - gridbox.setHorizontalSpacing(20) - gridbox.addWidget(hierarchy_label, 0, 0) - gridbox.addWidget(hierarchy_template, 0, 1, 1, 2) - gridbox.addWidget(start_frame_label, 0, 3) - gridbox.addWidget(start_frame, 0, 3, 1, 1) - gridbox.addWidget(tree, 1, 0, 5, 5) - gridbox.addWidget(select_all_btn, 6, 3) - gridbox.addWidget(ftrack_send_btn, 6, 4) + prop_layout = QtWidgets.QGridLayout() + prop_layout.setHorizontalSpacing(30) + prop_layout.addWidget(hierarchy_label, 0, 0) + prop_layout.addWidget(hierarchy_template_input, 0, 1) + prop_layout.addWidget(start_frame_label, 1, 0) + prop_layout.addWidget(start_frame_input, 1, 1) + prop_layout.addWidget(handles_label, 2, 0) + prop_layout.addWidget(handles_input, 2, 1) + prop_layout.addWidget(task_type_label, 3, 0) + prop_layout.addWidget(task_type_input, 3, 1) - window.setLayout(gridbox) + tree_layout = QtWidgets.QGridLayout() + tree_layout.addWidget(tree, 1, 0) + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(select_all_btn) + hbox.addWidget(ftrack_send_btn) + + main_frame = QtWidgets.QVBoxLayout() + main_frame.setMargin(20) + main_frame.addStretch(5) + main_frame.addLayout(prop_layout) + main_frame.addStretch(10) + main_frame.addLayout(tree_layout) + main_frame.addStretch(5) + main_frame.addLayout(hbox) + + + window.setLayout(main_frame) window.show() timeline_info(selection) From db875837e72afc0595c25adb8d514011fbafe8a6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 1 Nov 2021 17:27:29 +0100 Subject: [PATCH 16/58] ftrack task types populated to gui offer --- .../openpype_flame_to_ftrack.py | 165 +++++++++++------- 1 file changed, 103 insertions(+), 62 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 402f3918c3..8247dd0551 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -18,7 +18,6 @@ FTRACK_SERVER = None SCRIPT_DIR = os.path.dirname(__file__) EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") - @contextmanager def maintained_ftrack_session(): import ftrack_api @@ -233,7 +232,52 @@ class FlameButton(QtWidgets.QPushButton): 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') +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) + """ + + def __init__(self, button_name, menu_options, parent_window, *args, **kwargs): + super(FlamePushButtonMenu, self).__init__(*args, **kwargs) + from functools import partial + + self.setText(button_name) + 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"}' + 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') + + def create_menu(option): + self.setText(option) + + pushbutton_menu = QtWidgets.QMenu(parent_window) + pushbutton_menu.setFocusPolicy(QtCore.Qt.NoFocus) + pushbutton_menu.setStyleSheet('QMenu {color: #9a9a9a; background-color:#24303d; font: 14px "Discreet"}' + 'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}') + for option in menu_options: + pushbutton_menu.addAction(option, partial(create_menu, option)) + + self.setMenu(pushbutton_menu) + + def main_window(selection): + import flame + import six + import sys + import re + def timecode_to_frames(timecode, framerate): def _seconds(value): @@ -306,13 +350,8 @@ def main_window(selection): tree.selectAll() def send_to_ftrack(): - import flame - import six - import sys - import re - def create_ftrack_entity(session, type, name, parent=None): - parent = parent or f_project + parent = parent or F_PROJ_ENTITY entity = session.create(type, { 'name': name, 'parent': parent @@ -328,7 +367,7 @@ def main_window(selection): def get_ftrack_entity(session, type, name, parent): query = '{} where name is "{}" and project_id is "{}"'.format( - type, name, f_project["id"]) + type, name, F_PROJ_ENTITY["id"]) try: entity = session.query(query).one() @@ -381,8 +420,7 @@ def main_window(selection): "name": task_type.lower(), "parent": parent }) - task_types = get_all_task_types(f_project) - task["type"] = task_types[task_type] + task["type"] = F_PROJ_TASK_TYPES[task_type] return task @@ -400,15 +438,6 @@ def main_window(selection): print("tempdir_path: {}".format(tempdir_path)) print("Ftrack session is: {}".format(session)) - # get project name from flame current project - 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(project_name) - f_project = session.query(query).one() - print("Ftrack project is: {}".format(f_project)) - # get hanldes from gui input handles = handles_input.text() handle_start = int(handles) @@ -561,60 +590,72 @@ def main_window(selection): # Prevent weird characters when shrinking tree columns tree.setTextElideMode(QtCore.Qt.ElideNone) - # input fields - hierarchy_label = FlameLabel( - 'Parents template', 'normal', window) - hierarchy_template_input = FlameLineEdit(HIERARCHY_TEMPLATE, window) + with maintained_ftrack_session() as _session: + # input fields + hierarchy_label = FlameLabel( + 'Parents template', 'normal', window) + hierarchy_template_input = FlameLineEdit(HIERARCHY_TEMPLATE, window) - start_frame_label = FlameLabel( - 'Workfile start frame', 'normal', window) - start_frame_input = FlameLineEdit(str(WORKFILE_START_FRAME), window) + start_frame_label = FlameLabel( + 'Workfile start frame', 'normal', window) + start_frame_input = FlameLineEdit(str(WORKFILE_START_FRAME), window) - handles_label = FlameLabel( - 'Shot handles', 'normal', window) - handles_input = FlameLineEdit(str(5), window) + handles_label = FlameLabel( + 'Shot handles', 'normal', window) + handles_input = FlameLineEdit(str(5), window) - task_type_label = FlameLabel( - 'Create Task (type)', 'normal', window) - task_type_input = FlameLineEdit(CREATE_TASK_TYPE, window) + # get project name from flame current project + project_name = flame.project.current_project.name - ## Button - select_all_btn = FlameButton('Select All', select_all, window) - ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) + # get project from ftrack - + # ftrack project name has to be the same as flame project! + query = 'Project where full_name is "{}"'.format(project_name) - ## Window Layout - prop_layout = QtWidgets.QGridLayout() - prop_layout.setHorizontalSpacing(30) - prop_layout.addWidget(hierarchy_label, 0, 0) - prop_layout.addWidget(hierarchy_template_input, 0, 1) - prop_layout.addWidget(start_frame_label, 1, 0) - prop_layout.addWidget(start_frame_input, 1, 1) - prop_layout.addWidget(handles_label, 2, 0) - prop_layout.addWidget(handles_input, 2, 1) - prop_layout.addWidget(task_type_label, 3, 0) - prop_layout.addWidget(task_type_input, 3, 1) + # globally used variables + F_PROJ_ENTITY = _session.query(query).one() + F_PROJ_TASK_TYPES = get_all_task_types(F_PROJ_ENTITY) - tree_layout = QtWidgets.QGridLayout() - tree_layout.addWidget(tree, 1, 0) + task_type_label = FlameLabel( + 'Create Task (type)', 'normal', window) + task_type_input = FlamePushButtonMenu( + CREATE_TASK_TYPE, F_PROJ_TASK_TYPES, window) - hbox = QtWidgets.QHBoxLayout() - hbox.addWidget(select_all_btn) - hbox.addWidget(ftrack_send_btn) + ## Button + select_all_btn = FlameButton('Select All', select_all, window) + ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) - main_frame = QtWidgets.QVBoxLayout() - main_frame.setMargin(20) - main_frame.addStretch(5) - main_frame.addLayout(prop_layout) - main_frame.addStretch(10) - main_frame.addLayout(tree_layout) - main_frame.addStretch(5) - main_frame.addLayout(hbox) + ## Window Layout + prop_layout = QtWidgets.QGridLayout() + prop_layout.setHorizontalSpacing(30) + prop_layout.addWidget(hierarchy_label, 0, 0) + prop_layout.addWidget(hierarchy_template_input, 0, 1) + prop_layout.addWidget(start_frame_label, 1, 0) + prop_layout.addWidget(start_frame_input, 1, 1) + prop_layout.addWidget(handles_label, 2, 0) + prop_layout.addWidget(handles_input, 2, 1) + prop_layout.addWidget(task_type_label, 3, 0) + prop_layout.addWidget(task_type_input, 3, 1) + tree_layout = QtWidgets.QGridLayout() + tree_layout.addWidget(tree, 1, 0) - window.setLayout(main_frame) - window.show() + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(select_all_btn) + hbox.addWidget(ftrack_send_btn) - timeline_info(selection) + main_frame = QtWidgets.QVBoxLayout() + main_frame.setMargin(20) + main_frame.addStretch(5) + main_frame.addLayout(prop_layout) + main_frame.addStretch(10) + main_frame.addLayout(tree_layout) + main_frame.addStretch(5) + main_frame.addLayout(hbox) + + window.setLayout(main_frame) + window.show() + + timeline_info(selection) return window From 886bc81b4925db247f37e4428f24b991e6a85a86 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 Nov 2021 16:01:30 +0100 Subject: [PATCH 17/58] adding config settings.ini for retreating used data --- .../openpype_flame_to_ftrack.py | 173 +++++++++++++----- 1 file changed, 131 insertions(+), 42 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 8247dd0551..4b12cc651c 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -3,13 +3,11 @@ import os from PySide2 import QtWidgets, QtCore from pprint import pformat from contextlib import contextmanager +from xml.etree import ElementTree as ET +import ConfigParser as CP +import io -# Constants -WORKFILE_START_FRAME = 1001 -HIERARCHY_TEMPLATE = "shots[Folder]/{sequence}[Sequence]" -CREATE_TASK_TYPE = "Compositing" - # Fill following constants or set them via environment variable FTRACK_API_KEY = None FTRACK_API_USER = None @@ -17,6 +15,8 @@ FTRACK_SERVER = None SCRIPT_DIR = os.path.dirname(__file__) EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") +CONFIG_DIR = os.path.join(os.path.expanduser("~/.openpype"), "config") + @contextmanager def maintained_ftrack_session(): @@ -98,15 +98,82 @@ def make_temp_dir(): yield dirpath except IOError as _error: - raise IOError( - "Not able to create temp dir file: {}".format( - _error - ) - ) + raise IOError("Not able to create temp dir file: {}".format(_error)) finally: print(dirpath) - # shutil.rmtree(dirpath) + shutil.rmtree(dirpath) + + +@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 +hierarchy_template = shots[Folder]/{sequence}[Sequence] +create_task_type = Compositing +""" def get_all_task_types(project_entity): @@ -121,6 +188,18 @@ def get_all_task_types(project_entity): return tasks +def export_thumbnail(sequence, tempdir_path): + import flame + export_preset = os.path.join( + EXPORT_PRESETS_DIR, + "openpype_seg_thumbnails_jpg.xml" + ) + print(export_preset) + poster_frame_exporter = flame.PyExporter() + poster_frame_exporter.foreground = True + poster_frame_exporter.export(sequence, export_preset, tempdir_path) + + class FlameLabel(QtWidgets.QLabel): """ Custom Qt Flame Label Widget @@ -424,30 +503,39 @@ def main_window(selection): return task - def export_thumbnail(sequence, tempdir_path): - export_preset = os.path.join( - EXPORT_PRESETS_DIR, - "openpype_seg_thumbnails_jpg.xml" - ) - poster_frame_exporter = flame.PyExporter() - poster_frame_exporter.foreground = True - poster_frame_exporter.export(sequence, export_preset, tempdir_path) - # start procedure - with maintained_ftrack_session() as session, make_temp_dir() as tempdir_path: + _cfg_data_back = {} + # get hierarchy from gui input + hierarchy_text = hierarchy_template_input.text() + + # get hanldes from gui input + handles = handles_input.text() + + # get frame start from gui input + frame_start = int(start_frame_input.text()) + + # get task type from gui input + task_type = task_type_input.text() + + _cfg_data_back = { + "workfile_start_frame": str(frame_start), + "shot_handles": handles, + "hierarchy_template": hierarchy_text, + "create_task_type": task_type + } + ######################################################### + print(pformat(_cfg_data_back)) + # add cfg data back to settings.ini + set_config(_cfg_data_back, "main") + + with maintained_ftrack_session() as session, \ + make_temp_dir() as tempdir_path: print("tempdir_path: {}".format(tempdir_path)) print("Ftrack session is: {}".format(session)) - # get hanldes from gui input - handles = handles_input.text() - handle_start = int(handles) - handle_end = int(handles) - - # get frame start from gui input - frame_start = int(start_frame_input.text()) - - # get task type from gui input - task_type = task_type_input.text() + for seq in selection: + export_thumbnail(seq, tempdir_path) + break # Get all selected items from treewidget for item in tree.selectedItems(): @@ -471,12 +559,11 @@ def main_window(selection): } # format hierarchy template - hierarchy_text = hierarchy_template_input.text() - hierarchy_text = hierarchy_text.format(**shot_attributes) - print(hierarchy_text) + _hierarchy_text = hierarchy_text.format(**shot_attributes) + print(_hierarchy_text) # solve parents - parents = generate_parents_from_template(hierarchy_text) + parents = generate_parents_from_template(_hierarchy_text) print(parents) # obtain shot parents entities @@ -504,8 +591,8 @@ def main_window(selection): custom_attrs = { "frameStart": frame_start, "frameEnd": frame_end, - "handleStart": handle_start, - "handleEnd": handle_end + "handleStart": int(handles), + "handleEnd": int(handles) } # update custom attributes on shot entity @@ -590,19 +677,21 @@ def main_window(selection): # Prevent weird characters when shrinking tree columns tree.setTextElideMode(QtCore.Qt.ElideNone) - with maintained_ftrack_session() as _session: + with maintained_ftrack_session() as _session, get_config("main") as cfg_d: # input fields hierarchy_label = FlameLabel( 'Parents template', 'normal', window) - hierarchy_template_input = FlameLineEdit(HIERARCHY_TEMPLATE, window) + hierarchy_template_input = FlameLineEdit( + cfg_d["hierarchy_template"], window) start_frame_label = FlameLabel( 'Workfile start frame', 'normal', window) - start_frame_input = FlameLineEdit(str(WORKFILE_START_FRAME), window) + start_frame_input = FlameLineEdit( + cfg_d["workfile_start_frame"], window) handles_label = FlameLabel( 'Shot handles', 'normal', window) - handles_input = FlameLineEdit(str(5), window) + handles_input = FlameLineEdit(cfg_d["shot_handles"], window) # get project name from flame current project project_name = flame.project.current_project.name @@ -618,7 +707,7 @@ def main_window(selection): task_type_label = FlameLabel( 'Create Task (type)', 'normal', window) task_type_input = FlamePushButtonMenu( - CREATE_TASK_TYPE, F_PROJ_TASK_TYPES, window) + cfg_d["create_task_type"], F_PROJ_TASK_TYPES, window) ## Button select_all_btn = FlameButton('Select All', select_all, window) From 94c343e671e6ee55eb7338f789d2d4915cf0c93a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 Nov 2021 16:01:49 +0100 Subject: [PATCH 18/58] removing codec token from mov preset --- .../export_preset/openpype_seg_video_h264.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_video_h264.xml b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_video_h264.xml index 64447b76e8..3ca185b8b4 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_video_h264.xml +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/export_preset/openpype_seg_video_h264.xml @@ -27,7 +27,7 @@ QuickTime - <segment name>_<video codec> + <segment name> 0 PCS_709 None From 0948bf89e2d1298e854128f5934eb18693dcf110 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 Nov 2021 17:26:26 +0100 Subject: [PATCH 19/58] adding `source resolution` toggle --- .../openpype_flame_to_ftrack.py | 96 ++++++++++++++----- 1 file changed, 71 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 4b12cc651c..acbd87d91b 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -173,6 +173,7 @@ workfile_start_frame = 1001 shot_handles = 0 hierarchy_template = shots[Folder]/{sequence}[Sequence] create_task_type = Compositing +source_resolution = 0 """ @@ -277,12 +278,14 @@ class FlameTreeWidget(QtWidgets.QTreeWidget): 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"}' - 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' - 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' - 'QTreeWidget::item:selected {selection-background-color: #111111}' - 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' - 'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}') + self.setStyleSheet( + 'QTreeWidget {color: #9a9a9a; background-color: #2a2a2a; alternate-background-color: #2d2d2d; font: 14px "Discreet"}' + 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' + 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' + 'QTreeWidget::item:selected {selection-background-color: #111111}' + 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' + 'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}' + ) self.verticalScrollBar().setStyleSheet('color: #818181') self.horizontalScrollBar().setStyleSheet('color: #818181') self.setHeaderLabels(tree_headers) @@ -311,6 +314,31 @@ class FlameButton(QtWidgets.QPushButton): 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') +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"}' + '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}' + '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}' + 'QToolTip {color: black; background-color: #ffffde; border: black solid 1px}') + + class FlamePushButtonMenu(QtWidgets.QPushButton): """ Custom Qt Flame Menu Push Button Widget @@ -521,10 +549,12 @@ def main_window(selection): "workfile_start_frame": str(frame_start), "shot_handles": handles, "hierarchy_template": hierarchy_text, - "create_task_type": task_type + "create_task_type": task_type, + "source_resolution": ( + "1" if source_resolution_btn.isChecked() else "0") } ######################################################### - print(pformat(_cfg_data_back)) + # add cfg data back to settings.ini set_config(_cfg_data_back, "main") @@ -693,6 +723,12 @@ def main_window(selection): 'Shot handles', 'normal', window) handles_input = FlameLineEdit(cfg_d["shot_handles"], window) + source_resolution_btn = FlamePushButton( + 'Source resolutuion', bool(int(cfg_d["source_resolution"])), + window + ) + + # get project name from flame current project project_name = flame.project.current_project.name @@ -713,35 +749,45 @@ def main_window(selection): select_all_btn = FlameButton('Select All', select_all, window) ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) - ## Window Layout - prop_layout = QtWidgets.QGridLayout() - prop_layout.setHorizontalSpacing(30) - prop_layout.addWidget(hierarchy_label, 0, 0) - prop_layout.addWidget(hierarchy_template_input, 0, 1) - prop_layout.addWidget(start_frame_label, 1, 0) - prop_layout.addWidget(start_frame_input, 1, 1) - prop_layout.addWidget(handles_label, 2, 0) - prop_layout.addWidget(handles_input, 2, 1) - prop_layout.addWidget(task_type_label, 3, 0) - prop_layout.addWidget(task_type_input, 3, 1) + ## left props + prop_layout_l = QtWidgets.QGridLayout() + prop_layout_l.setHorizontalSpacing(30) + prop_layout_l.addWidget(hierarchy_label, 0, 0) + prop_layout_l.addWidget(hierarchy_template_input, 0, 1) + prop_layout_l.addWidget(start_frame_label, 1, 0) + prop_layout_l.addWidget(start_frame_input, 1, 1) + prop_layout_l.addWidget(handles_label, 2, 0) + prop_layout_l.addWidget(handles_input, 2, 1) + prop_layout_l.addWidget(task_type_label, 3, 0) + prop_layout_l.addWidget(task_type_input, 3, 1) - tree_layout = QtWidgets.QGridLayout() - tree_layout.addWidget(tree, 1, 0) + # right props + prop_widget_r = QtWidgets.QWidget(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(source_resolution_btn, 0, 0) + + 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) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(select_all_btn) hbox.addWidget(ftrack_send_btn) - main_frame = QtWidgets.QVBoxLayout() + main_frame = QtWidgets.QVBoxLayout(window) main_frame.setMargin(20) main_frame.addStretch(5) - main_frame.addLayout(prop_layout) + main_frame.addLayout(prop_main_layout) main_frame.addStretch(10) - main_frame.addLayout(tree_layout) + main_frame.addWidget(tree) main_frame.addStretch(5) main_frame.addLayout(hbox) - window.setLayout(main_frame) window.show() timeline_info(selection) From fd435043c42a9e7d571feb4db1dc6246fd47a5c5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 Nov 2021 18:16:59 +0100 Subject: [PATCH 20/58] adding resolution and fps attributes --- .../openpype_flame_to_ftrack.py | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index acbd87d91b..54455c7139 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -419,7 +419,7 @@ def main_window(selection): for ver in sequence.versions: for tracks in ver.tracks: for segment in tracks.segments: - print(segment.type) + print(segment.attributes) # get clip frame duration record_duration = str(segment.record_duration)[1:-1] clip_duration = timecode_to_frames( @@ -545,6 +545,12 @@ def main_window(selection): # get task type from gui input task_type = task_type_input.text() + # get resolution from gui inputs + width = width_input.text() + height = height_input.text() + pixel_aspect = pixel_aspect_input.text() + fps = fps_input.text() + _cfg_data_back = { "workfile_start_frame": str(frame_start), "shot_handles": handles, @@ -622,7 +628,11 @@ def main_window(selection): "frameStart": frame_start, "frameEnd": frame_end, "handleStart": int(handles), - "handleEnd": int(handles) + "handleEnd": int(handles), + "resolutionWidth": int(width), + "resolutionHeight": int(height), + "pixelAspect": float(pixel_aspect), + "fps": float(fps) } # update custom attributes on shot entity @@ -708,6 +718,13 @@ def main_window(selection): tree.setTextElideMode(QtCore.Qt.ElideNone) with maintained_ftrack_session() as _session, get_config("main") as cfg_d: + + for select in selection: + seq_height = select.height + seq_width = select.width + fps = float(str(select.frame_rate)[:-4]) + break + # input fields hierarchy_label = FlameLabel( 'Parents template', 'normal', window) @@ -728,6 +745,21 @@ def main_window(selection): window ) + width_label = FlameLabel( + 'Sequence width', 'normal', window) + width_input = FlameLineEdit(str(seq_width), window) + + height_label = FlameLabel( + 'Sequence height', 'normal', window) + height_input = FlameLineEdit(str(seq_height), window) + + pixel_aspect_label = FlameLabel( + 'Pixel aspect ratio', 'normal', window) + pixel_aspect_input = FlameLineEdit(str(1.00), window) + + fps_label = FlameLabel( + 'Frame rate', 'normal', window) + fps_input = FlameLineEdit(str(fps), window) # get project name from flame current project project_name = flame.project.current_project.name @@ -769,6 +801,14 @@ def main_window(selection): QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) prop_layout_r.setContentsMargins(0, 0, 0, 0) prop_layout_r.addWidget(source_resolution_btn, 0, 0) + prop_layout_r.addWidget(width_label, 1, 0) + prop_layout_r.addWidget(width_input, 1, 1) + prop_layout_r.addWidget(height_label, 2, 0) + prop_layout_r.addWidget(height_input, 2, 1) + prop_layout_r.addWidget(pixel_aspect_label, 3, 0) + prop_layout_r.addWidget(pixel_aspect_input, 3, 1) + prop_layout_r.addWidget(fps_label, 4, 0) + prop_layout_r.addWidget(fps_input, 4, 1) prop_main_layout = QtWidgets.QHBoxLayout() prop_main_layout.addLayout(prop_layout_l, 1) From 56656b5e2d4cd9fdaf5a4d3e43f7c85c99cca963 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 Nov 2021 21:04:09 +0100 Subject: [PATCH 21/58] shot name template --- .../openpype_flame_to_ftrack.py | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 54455c7139..1c9ed0583e 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -171,6 +171,7 @@ def cfg_default(): [main] workfile_start_frame = 1001 shot_handles = 0 +shot_name_template = {sequence}_{shot} hierarchy_template = shots[Folder]/{sequence}[Sequence] create_task_type = Compositing source_resolution = 0 @@ -521,7 +522,7 @@ def main_window(selection): ] if existing_task: - return existing_task + return existing_task.pop() task = session.create('Task', { "name": task_type.lower(), @@ -533,6 +534,10 @@ def main_window(selection): # start procedure _cfg_data_back = {} + + # get shot name template from gui input + shot_name_template = shot_name_template_input.text() + # get hierarchy from gui input hierarchy_text = hierarchy_template_input.text() @@ -552,6 +557,7 @@ def main_window(selection): fps = fps_input.text() _cfg_data_back = { + "shot_name_template": shot_name_template, "workfile_start_frame": str(frame_start), "shot_handles": handles, "hierarchy_template": hierarchy_text, @@ -594,6 +600,9 @@ def main_window(selection): "task": task_type } + # format shot name template + _shot_name = shot_name_template.format(**shot_attributes) + # format hierarchy template _hierarchy_text = hierarchy_text.format(**shot_attributes) print(_hierarchy_text) @@ -618,7 +627,7 @@ def main_window(selection): f_s_entity = get_ftrack_entity( session, "Shot", - item.text(1), + _shot_name, _parent ) print("Shot entity is: {}".format(f_s_entity)) @@ -726,6 +735,11 @@ def main_window(selection): break # input fields + shot_name_label = FlameLabel( + 'Shot name template', 'normal', window) + shot_name_template_input = FlameLineEdit( + cfg_d["shot_name_template"], window) + hierarchy_label = FlameLabel( 'Parents template', 'normal', window) hierarchy_template_input = FlameLineEdit( @@ -784,14 +798,16 @@ def main_window(selection): ## left props prop_layout_l = QtWidgets.QGridLayout() prop_layout_l.setHorizontalSpacing(30) - prop_layout_l.addWidget(hierarchy_label, 0, 0) - prop_layout_l.addWidget(hierarchy_template_input, 0, 1) - prop_layout_l.addWidget(start_frame_label, 1, 0) - prop_layout_l.addWidget(start_frame_input, 1, 1) - prop_layout_l.addWidget(handles_label, 2, 0) - prop_layout_l.addWidget(handles_input, 2, 1) - prop_layout_l.addWidget(task_type_label, 3, 0) - prop_layout_l.addWidget(task_type_input, 3, 1) + prop_layout_l.addWidget(shot_name_label, 0, 0) + prop_layout_l.addWidget(shot_name_template_input, 0, 1) + prop_layout_l.addWidget(hierarchy_label, 1, 0) + prop_layout_l.addWidget(hierarchy_template_input, 1, 1) + prop_layout_l.addWidget(start_frame_label, 2, 0) + prop_layout_l.addWidget(start_frame_input, 2, 1) + prop_layout_l.addWidget(handles_label, 3, 0) + prop_layout_l.addWidget(handles_input, 3, 1) + prop_layout_l.addWidget(task_type_label, 4, 0) + prop_layout_l.addWidget(task_type_input, 4, 1) # right props prop_widget_r = QtWidgets.QWidget(window) @@ -821,11 +837,8 @@ def main_window(selection): main_frame = QtWidgets.QVBoxLayout(window) main_frame.setMargin(20) - main_frame.addStretch(5) main_frame.addLayout(prop_main_layout) - main_frame.addStretch(10) main_frame.addWidget(tree) - main_frame.addStretch(5) main_frame.addLayout(hbox) window.show() @@ -852,5 +865,4 @@ def get_media_panel_custom_ui_actions(): } ] } - ] From c3e1c81318abe4da49d2b3de4ae4c715eca1883e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 Nov 2021 21:05:07 +0100 Subject: [PATCH 22/58] ftrack module path import from environment specific path --- .../openpype_flame_to_ftrack.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 1c9ed0583e..0e25200ac4 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -7,21 +7,34 @@ from xml.etree import ElementTree as ET import ConfigParser as CP import io - # Fill following constants or set them via environment variable +FTRACK_MODULE_PATH = None FTRACK_API_KEY = None FTRACK_API_USER = None FTRACK_SERVER = None SCRIPT_DIR = os.path.dirname(__file__) EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") -CONFIG_DIR = os.path.join(os.path.expanduser("~/.openpype"), "config") +CONFIG_DIR = os.path.join(os.path.expanduser( + "~/.openpype"), "openpype_flame_to_ftrack") + + +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 @contextmanager def maintained_ftrack_session(): - import ftrack_api import os + ftrack_api = import_ftrack_api() def validate_credentials(url, user, api): first_validation = True From faee1ba80813645e6192faf9d25796bed308a44a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 Nov 2021 21:06:43 +0100 Subject: [PATCH 23/58] style improvements --- .../openpype_flame_to_ftrack/openpype_flame_to_ftrack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 0e25200ac4..76719fa509 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -804,11 +804,11 @@ def main_window(selection): task_type_input = FlamePushButtonMenu( cfg_d["create_task_type"], F_PROJ_TASK_TYPES, window) - ## Button + # Button select_all_btn = FlameButton('Select All', select_all, window) ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) - ## left props + # left props prop_layout_l = QtWidgets.QGridLayout() prop_layout_l.setHorizontalSpacing(30) prop_layout_l.addWidget(shot_name_label, 0, 0) From ca3e0280d5a2103f7f1b0990e78a0cdc73ed4296 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 Nov 2021 21:36:26 +0100 Subject: [PATCH 24/58] thumbnails and videos wip --- .../openpype_flame_to_ftrack.py | 89 ++++++++++++------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 76719fa509..f2f96281a8 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -209,12 +209,47 @@ def export_thumbnail(sequence, tempdir_path): EXPORT_PRESETS_DIR, "openpype_seg_thumbnails_jpg.xml" ) - print(export_preset) poster_frame_exporter = flame.PyExporter() poster_frame_exporter.foreground = True poster_frame_exporter.export(sequence, export_preset, tempdir_path) +def export_video(sequence, tempdir_path): + import flame + export_preset = os.path.join( + EXPORT_PRESETS_DIR, + "openpype_seg_video_h264.xml" + ) + poster_frame_exporter = flame.PyExporter() + poster_frame_exporter.foreground = True + poster_frame_exporter.export(sequence, export_preset, 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 + + class FlameLabel(QtWidgets.QLabel): """ Custom Qt Flame Label Widget @@ -399,34 +434,7 @@ def main_window(selection): import sys import re - 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 timecode_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(timecode_to_frames(timecode, start='00:00:00:00'))) - - return frames - def timeline_info(selection): - import flame - # identificar as informacoes dos segmentos na timeline for sequence in selection: frame_rate = float(str(sequence.frame_rate)[:-4]) @@ -545,7 +553,9 @@ def main_window(selection): return task - # start procedure + ''' + ##################### start procedure + ''' _cfg_data_back = {} # get shot name template from gui input @@ -578,7 +588,6 @@ def main_window(selection): "source_resolution": ( "1" if source_resolution_btn.isChecked() else "0") } - ######################################################### # add cfg data back to settings.ini set_config(_cfg_data_back, "main") @@ -590,8 +599,17 @@ def main_window(selection): for seq in selection: export_thumbnail(seq, tempdir_path) + export_video(seq, tempdir_path) break + temp_files = os.listdir(tempdir_path) + thumbnails = [f for f in temp_files if "jpg" in f] + videos = [f for f in temp_files if "mov" in f] + + print(temp_files) + print(thumbnails) + print(videos) + # Get all selected items from treewidget for item in tree.selectedItems(): # frame ranges @@ -606,6 +624,17 @@ def main_window(selection): sequence_name = item.text(0) shot_name = item.text(1) + # get component files + thumb_f = next((f for f in thumbnails if shot_name in f), None) + video_f = next((f for f in videos if shot_name in f), None) + print(thumb_f) + print(video_f) + + thumb_fp = os.path.join(tempdir_path, thumb_f) + video_fp = os.path.join(tempdir_path, video_f) + print(thumb_fp) + print(video_fp) + # populate full shot info shot_attributes = { "sequence": sequence_name, From 9ec5d2f5a624bc6a57013e59f7230f5642bd5317 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 3 Nov 2021 14:42:48 +0100 Subject: [PATCH 25/58] project select gui --- .../openpype_flame_to_ftrack.py | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index f2f96281a8..ad2e66d477 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -556,6 +556,15 @@ def main_window(selection): ''' ##################### start procedure ''' + # resolve active project and add it to F_PROJ_ENTITY + if proj_selector: + selected_project_name = project_select_input.text() + F_PROJ_ENTITY = next( + (p for p in all_projects + if p["full_name"] is selected_project_name), + None + ) + _cfg_data_back = {} # get shot name template from gui input @@ -800,7 +809,6 @@ def main_window(selection): 'Source resolutuion', bool(int(cfg_d["source_resolution"])), window ) - width_label = FlameLabel( 'Sequence width', 'normal', window) width_input = FlameLineEdit(str(seq_width), window) @@ -826,6 +834,19 @@ def main_window(selection): # globally used variables F_PROJ_ENTITY = _session.query(query).one() + + proj_selector = bool(not F_PROJ_ENTITY) + + if proj_selector: + all_projects = _session.query( + "Project where status is active").all() + F_PROJ_ENTITY = all_projects[0] + project_names = [p["full_name"] for p in all_projects] + project_select_label = FlameLabel( + 'Select Ftrack project', 'normal', window) + project_select_input = FlamePushButtonMenu( + F_PROJ_ENTITY["full_name"], project_names, window) + F_PROJ_TASK_TYPES = get_all_task_types(F_PROJ_ENTITY) task_type_label = FlameLabel( @@ -838,18 +859,23 @@ def main_window(selection): ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) # left props + v_shift = 0 prop_layout_l = QtWidgets.QGridLayout() prop_layout_l.setHorizontalSpacing(30) - prop_layout_l.addWidget(shot_name_label, 0, 0) - prop_layout_l.addWidget(shot_name_template_input, 0, 1) - prop_layout_l.addWidget(hierarchy_label, 1, 0) - prop_layout_l.addWidget(hierarchy_template_input, 1, 1) - prop_layout_l.addWidget(start_frame_label, 2, 0) - prop_layout_l.addWidget(start_frame_input, 2, 1) - prop_layout_l.addWidget(handles_label, 3, 0) - prop_layout_l.addWidget(handles_input, 3, 1) - prop_layout_l.addWidget(task_type_label, 4, 0) - prop_layout_l.addWidget(task_type_input, 4, 1) + if proj_selector: + prop_layout_l.addWidget(project_select_label, v_shift, 0) + prop_layout_l.addWidget(project_select_input, v_shift, 1) + v_shift += 1 + prop_layout_l.addWidget(shot_name_label, (v_shift + 0), 0) + prop_layout_l.addWidget(shot_name_template_input, (v_shift + 0), 1) + prop_layout_l.addWidget(hierarchy_label, (v_shift + 1), 0) + prop_layout_l.addWidget(hierarchy_template_input, (v_shift + 1), 1) + prop_layout_l.addWidget(start_frame_label, (v_shift + 2), 0) + prop_layout_l.addWidget(start_frame_input, (v_shift + 2), 1) + prop_layout_l.addWidget(handles_label, (v_shift + 3), 0) + prop_layout_l.addWidget(handles_input, (v_shift + 3), 1) + prop_layout_l.addWidget(task_type_label, (v_shift + 4), 0) + prop_layout_l.addWidget(task_type_input, (v_shift + 4), 1) # right props prop_widget_r = QtWidgets.QWidget(window) @@ -868,15 +894,18 @@ def main_window(selection): prop_layout_r.addWidget(fps_label, 4, 0) prop_layout_r.addWidget(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(select_all_btn) hbox.addWidget(ftrack_send_btn) + # put all layouts together main_frame = QtWidgets.QVBoxLayout(window) main_frame.setMargin(20) main_frame.addLayout(prop_main_layout) From 90d4d4f4016f426a35b286db38cc78552b99b23e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 3 Nov 2021 16:36:13 +0100 Subject: [PATCH 26/58] interactive way of task type offer based on selected project --- .../openpype_flame_to_ftrack.py | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index ad2e66d477..053dff46ba 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -187,7 +187,6 @@ shot_handles = 0 shot_name_template = {sequence}_{shot} hierarchy_template = shots[Folder]/{sequence}[Sequence] create_task_type = Compositing -source_resolution = 0 """ @@ -402,12 +401,11 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): 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) - from functools import partial - self.setText(button_name) self.setParent(parent_window) self.setMinimumHeight(28) self.setMinimumWidth(110) @@ -415,17 +413,31 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): self.setStyleSheet('QPushButton {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') - def create_menu(option): - self.setText(option) pushbutton_menu = QtWidgets.QMenu(parent_window) pushbutton_menu.setFocusPolicy(QtCore.Qt.NoFocus) pushbutton_menu.setStyleSheet('QMenu {color: #9a9a9a; background-color:#24303d; font: 14px "Discreet"}' 'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}') - for option in menu_options: - pushbutton_menu.addAction(option, partial(create_menu, option)) + 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()) def main_window(selection): @@ -434,6 +446,10 @@ def main_window(selection): import sys import re + def _on_project_changed(project_name): + task_types = TASK_TYPES_ALL[project_name] + task_type_input.set_menu_options(task_types) + def timeline_info(selection): # identificar as informacoes dos segmentos na timeline for sequence in selection: @@ -593,9 +609,7 @@ def main_window(selection): "workfile_start_frame": str(frame_start), "shot_handles": handles, "hierarchy_template": hierarchy_text, - "create_task_type": task_type, - "source_resolution": ( - "1" if source_resolution_btn.isChecked() else "0") + "create_task_type": task_type } # add cfg data back to settings.ini @@ -805,10 +819,6 @@ def main_window(selection): 'Shot handles', 'normal', window) handles_input = FlameLineEdit(cfg_d["shot_handles"], window) - source_resolution_btn = FlamePushButton( - 'Source resolutuion', bool(int(cfg_d["source_resolution"])), - window - ) width_label = FlameLabel( 'Sequence width', 'normal', window) width_input = FlameLineEdit(str(seq_width), window) @@ -833,7 +843,7 @@ def main_window(selection): query = 'Project where full_name is "{}"'.format(project_name) # globally used variables - F_PROJ_ENTITY = _session.query(query).one() + F_PROJ_ENTITY = _session.query(query).first() proj_selector = bool(not F_PROJ_ENTITY) @@ -842,17 +852,19 @@ def main_window(selection): "Project where status is active").all() F_PROJ_ENTITY = all_projects[0] project_names = [p["full_name"] for p in all_projects] + TASK_TYPES_ALL = {p["full_name"]: get_all_task_types(p).keys() for p in all_projects} project_select_label = FlameLabel( 'Select Ftrack project', 'normal', window) project_select_input = FlamePushButtonMenu( F_PROJ_ENTITY["full_name"], project_names, window) + project_select_input.selection_changed.connect(_on_project_changed) F_PROJ_TASK_TYPES = get_all_task_types(F_PROJ_ENTITY) task_type_label = FlameLabel( 'Create Task (type)', 'normal', window) task_type_input = FlamePushButtonMenu( - cfg_d["create_task_type"], F_PROJ_TASK_TYPES, window) + cfg_d["create_task_type"], F_PROJ_TASK_TYPES.keys(), window) # Button select_all_btn = FlameButton('Select All', select_all, window) @@ -884,7 +896,6 @@ def main_window(selection): prop_layout_r.setAlignment( QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) prop_layout_r.setContentsMargins(0, 0, 0, 0) - prop_layout_r.addWidget(source_resolution_btn, 0, 0) prop_layout_r.addWidget(width_label, 1, 0) prop_layout_r.addWidget(width_input, 1, 1) prop_layout_r.addWidget(height_label, 2, 0) From 6d89bbc7a73c60f43804cd64fb8f3c7178d515fe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 3 Nov 2021 17:24:56 +0100 Subject: [PATCH 27/58] improving way of working with temp data --- .../openpype_flame_to_ftrack.py | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 053dff46ba..54935ccb97 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -1,5 +1,8 @@ from __future__ import print_function import os +import six +import sys +import re from PySide2 import QtWidgets, QtCore from pprint import pformat from contextlib import contextmanager @@ -13,6 +16,9 @@ FTRACK_API_KEY = None FTRACK_API_USER = None FTRACK_SERVER = None +TEMP_DIR_DATA_PATH = None +F_PROJ_ENTITY = None + SCRIPT_DIR = os.path.dirname(__file__) EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") CONFIG_DIR = os.path.join(os.path.expanduser( @@ -92,9 +98,9 @@ def maintained_ftrack_session(): api_key=api ) yield session - except Exception as _E: - print( - "ERROR: {}".format(_E)) + except Exception: + tp, value, tb = sys.exc_info() + six.reraise(tp, value, tb) finally: # close the session session.close() @@ -103,7 +109,6 @@ def maintained_ftrack_session(): @contextmanager def make_temp_dir(): import tempfile - import shutil try: dirpath = tempfile.mkdtemp() @@ -114,8 +119,7 @@ def make_temp_dir(): raise IOError("Not able to create temp dir file: {}".format(_error)) finally: - print(dirpath) - shutil.rmtree(dirpath) + pass @contextmanager @@ -413,7 +417,6 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): self.setStyleSheet('QPushButton {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') - pushbutton_menu = QtWidgets.QMenu(parent_window) pushbutton_menu.setFocusPolicy(QtCore.Qt.NoFocus) pushbutton_menu.setStyleSheet('QMenu {color: #9a9a9a; background-color:#24303d; font: 14px "Discreet"}' @@ -442,9 +445,8 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): def main_window(selection): import flame - import six - import sys - import re + global TEMP_DIR_DATA_PATH + global F_PROJ_ENTITY def _on_project_changed(project_name): task_types = TASK_TYPES_ALL[project_name] @@ -494,8 +496,28 @@ def main_window(selection): tree.selectAll() + def remove_temp_data(): + global TEMP_DIR_DATA_PATH + import shutil + if TEMP_DIR_DATA_PATH: + shutil.rmtree(TEMP_DIR_DATA_PATH) + TEMP_DIR_DATA_PATH = None + + def generate_temp_data(): + global TEMP_DIR_DATA_PATH + if TEMP_DIR_DATA_PATH: + return True + + with make_temp_dir() as tempdir_path: + for seq in selection: + export_thumbnail(seq, tempdir_path) + export_video(seq, tempdir_path) + TEMP_DIR_DATA_PATH = tempdir_path + break + def send_to_ftrack(): def create_ftrack_entity(session, type, name, parent=None): + global F_PROJ_ENTITY parent = parent or F_PROJ_ENTITY entity = session.create(type, { 'name': name, @@ -511,6 +533,7 @@ def main_window(selection): return entity def get_ftrack_entity(session, type, name, parent): + global F_PROJ_ENTITY query = '{} where name is "{}" and project_id is "{}"'.format( type, name, F_PROJ_ENTITY["id"]) @@ -577,7 +600,7 @@ def main_window(selection): selected_project_name = project_select_input.text() F_PROJ_ENTITY = next( (p for p in all_projects - if p["full_name"] is selected_project_name), + if p["full_name"] in selected_project_name), None ) @@ -615,17 +638,12 @@ def main_window(selection): # add cfg data back to settings.ini set_config(_cfg_data_back, "main") - with maintained_ftrack_session() as session, \ - make_temp_dir() as tempdir_path: - print("tempdir_path: {}".format(tempdir_path)) + with maintained_ftrack_session() as session: print("Ftrack session is: {}".format(session)) - for seq in selection: - export_thumbnail(seq, tempdir_path) - export_video(seq, tempdir_path) - break + generate_temp_data() - temp_files = os.listdir(tempdir_path) + temp_files = os.listdir(TEMP_DIR_DATA_PATH) thumbnails = [f for f in temp_files if "jpg" in f] videos = [f for f in temp_files if "mov" in f] @@ -653,8 +671,8 @@ def main_window(selection): print(thumb_f) print(video_f) - thumb_fp = os.path.join(tempdir_path, thumb_f) - video_fp = os.path.join(tempdir_path, video_f) + thumb_fp = os.path.join(TEMP_DIR_DATA_PATH, thumb_f) + video_fp = os.path.join(TEMP_DIR_DATA_PATH, video_f) print(thumb_fp) print(video_fp) @@ -852,7 +870,8 @@ def main_window(selection): "Project where status is active").all() F_PROJ_ENTITY = all_projects[0] project_names = [p["full_name"] for p in all_projects] - TASK_TYPES_ALL = {p["full_name"]: get_all_task_types(p).keys() for p in all_projects} + TASK_TYPES_ALL = {p["full_name"]: get_all_task_types( + p).keys() for p in all_projects} project_select_label = FlameLabel( 'Select Ftrack project', 'normal', window) project_select_input = FlamePushButtonMenu( @@ -868,6 +887,9 @@ def main_window(selection): # Button select_all_btn = FlameButton('Select All', select_all, window) + remove_temp_data_btn = FlameButton( + 'Remove temp data', remove_temp_data, window) + ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) # left props @@ -913,6 +935,7 @@ def main_window(selection): # buttons layout hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(remove_temp_data_btn) hbox.addWidget(select_all_btn) hbox.addWidget(ftrack_send_btn) From 718f334752ebd2b223e5b13f29e082dcb7782f51 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Nov 2021 06:41:30 +0100 Subject: [PATCH 28/58] adding flame wiretap env into settings and allowing them in prelaunch hook --- openpype/hosts/flame/hooks/pre_flame_setup.py | 9 ++++++--- .../settings/defaults/system_settings/applications.json | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 368a70f395..9043eb6cc6 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -2,6 +2,7 @@ import os import json import tempfile import contextlib +import socket from openpype.lib import ( PreLaunchHook, get_openpype_username) from openpype.hosts import flame as opflame @@ -32,6 +33,7 @@ class FlamePrelaunch(PreLaunchHook): """Hook entry method.""" project_doc = self.data["project_doc"] user_name = get_openpype_username() + hostname = socket.gethostname() self.log.debug("Collected user \"{}\"".format(user_name)) self.log.info(pformat(project_doc)) @@ -53,11 +55,12 @@ class FlamePrelaunch(PreLaunchHook): "FieldDominance": "PROGRESSIVE" } + data_to_script = { # from settings - "host_name": "localhost", - "volume_name": "stonefs", - "group_name": "staff", + "host_name": os.getenv("FLAME_WIRETAP_HOSTNAME") or hostname, + "volume_name": os.getenv("FLAME_WIRETAP_VOLUME"), + "group_name": os.getenv("FLAME_WIRETAP_GROUP"), "color_policy": "ACES 1.1", # from project diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 79711f3067..c8871c338c 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -107,7 +107,10 @@ "windows": "", "darwin": "", "linux": "" - } + }, + "FLAME_WIRETAP_HOSTNAME": "", + "FLAME_WIRETAP_VOLUME": "stonefs", + "FLAME_WIRETAP_GROUP": "staff" }, "variants": { "2021": { From 9cc61f1b83629c87a2d0cfd962e1a323a1a2a2fd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Nov 2021 08:50:40 +0100 Subject: [PATCH 29/58] wip commit components of thumb and video --- .../openpype_flame_to_ftrack.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 54935ccb97..23bebe7c2e 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -3,6 +3,7 @@ import os import six import sys import re +import json from PySide2 import QtWidgets, QtCore from pprint import pformat from contextlib import contextmanager @@ -442,6 +443,95 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): self.setText(action.text()) self.selection_changed.emit(action.text()) +class FtrackComponentCreator: + default_location = "ftrack.server" + ftrack_locations = {} + + def __init__(self, session): + self.session = session + self.get_ftrack_location() + + def create_comonent(self, parent, component_data): + location = self.get_ftrack_location + + file_path = component_data["file_path"] + name = component_data["name"] + + # get extension + file = os.path.basename(file_path) + _name, ext = os.path.splitext(file) + + _component_data = { + "name": name, + "file_path": file_path, + "file_type": ext, + "location": location, + "overwrite": True + + } + + if name != "thumnail": + duration = component_data["duration"] + handles = component_data["handles"] + fps = component_data["fps"] + _component_data.update({ + "name": "ftrackreview-mp4", + "metadata": {'ftr_meta': json.dumps({ + 'frameIn': int(0), + 'frameOut': int(duration + handles), + 'frameRate': float(fps)})} + }) + + component_item = { + "component_data": _component_data, + "thumbnail": bool(name == "thumbnail") + } + + # get assettype entity from session + assettype_entity = self.get_assettype({"short": "reference"}) + + # get or create asset entity from session + asset_entity = self.get_asset( + assettype_entity, {"name": "plateReference"}) + # commit if created + + # get or create assetversion entity from session + assetversion_entity = self.get_assetversion( + asset_entity, {"version": 1}) + # commit if created + + # get or create component entity + # overwrite existing members in component enity + # - get data for member from `ftrack.origin` location + + def get_assettype(self, parent, data): + pass + + def get_asset(self, parent, data): + pass + + def get_assetversion(self, parent, data): + pass + + 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): + 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 main_window(selection): import flame From e80518facbac0f268d433ecb00472b17b45ea078 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Nov 2021 22:08:30 +0100 Subject: [PATCH 30/58] component generator finish --- .../openpype_flame_to_ftrack.py | 243 ++++++++++++++---- 1 file changed, 199 insertions(+), 44 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 23bebe7c2e..c12c1afb72 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -443,86 +443,181 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): self.setText(action.text()) self.selection_changed.emit(action.text()) + class FtrackComponentCreator: default_location = "ftrack.server" ftrack_locations = {} def __init__(self, session): self.session = session - self.get_ftrack_location() + self._get_ftrack_location() - def create_comonent(self, parent, component_data): - location = self.get_ftrack_location + def create_comonent(self, parent, data, assetversion_entity=None): + location = self._get_ftrack_location() - file_path = component_data["file_path"] - name = component_data["name"] + file_path = data["file_path"] # get extension file = os.path.basename(file_path) - _name, ext = os.path.splitext(file) + _n, ext = os.path.splitext(file) - _component_data = { + name = "ftrackreview-mp4" if "mov" in ext else "thumbnail" + + component_data = { "name": name, "file_path": file_path, "file_type": ext, - "location": location, - "overwrite": True - + "location": location } - if name != "thumnail": - duration = component_data["duration"] - handles = component_data["handles"] - fps = component_data["fps"] - _component_data.update({ - "name": "ftrackreview-mp4", + if name == "ftrackreview-mp4": + duration = data["duration"] + handles = data["handles"] + fps = data["fps"] + component_data.update({ "metadata": {'ftr_meta': json.dumps({ 'frameIn': int(0), 'frameOut': int(duration + handles), 'frameRate': float(fps)})} }) - component_item = { - "component_data": _component_data, - "thumbnail": bool(name == "thumbnail") - } + if not assetversion_entity: + # get assettype entity from session + assettype_entity = self._get_assettype({"short": "reference"}) - # 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": parent + }) - # get or create asset entity from session - asset_entity = self.get_asset( - assettype_entity, {"name": "plateReference"}) - # commit if created - - # get or create assetversion entity from session - assetversion_entity = self.get_assetversion( - asset_entity, {"version": 1}) - # commit if created + # get or create assetversion entity from session + assetversion_entity = self._get_assetversion({ + "version": 1, + "asset": asset_entity + }) # get or create component entity - # overwrite existing members in component enity - # - get data for member from `ftrack.origin` location + self._set_component(component_data, { + "name": name, + "version": assetversion_entity, + }) - def get_assettype(self, parent, data): - pass + return assetversion_entity - def get_asset(self, parent, data): - pass + def _overwrite_members(self, entity, data): + origin_location = self._get_ftrack_location("ftrack.origin") + location = data.pop("location") - def get_assetversion(self, parent, data): - pass + # Removing existing members from location + components = list(entity.get("members", [])) + components += [entity] + for component in components: + for loc in component["component_locations"]: + if location["id"] == loc["location_id"]: + location.remove_component( + component, recursive=False + ) - def commit(self): + # Deleting existing members on component entity + for member in entity.get("members", []): + self.session.delete(member) + del(member) + + self._commit() + + # Reset members in memory + if "members" in entity.keys(): + entity["members"] = [] + + entity["file_type"] = data["file_type"] + + origin_location.add_component( + entity, data["file_path"] + ) + + # Add components to location. + location.add_component( + entity, origin_location, recursive=True) + + 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 enity + # - get data for member from `ftrack.origin` location + self._overwrite_members(component_entity, comp_data) + 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": + 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() + 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): + def _get_ftrack_location(self, name=None): if name in self.ftrack_locations: return self.ftrack_locations[name] @@ -532,6 +627,48 @@ class FtrackComponentCreator: 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.iteritems(): + if not isinstance(value, (basestring, 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 + def main_window(selection): import flame @@ -614,7 +751,7 @@ def main_window(selection): 'parent': parent }) try: - session.commit() + session._commit() except Exception: tp, value, tb = sys.exc_info() session.rollback() @@ -731,6 +868,8 @@ def main_window(selection): with maintained_ftrack_session() as session: print("Ftrack session is: {}".format(session)) + component_creator = FtrackComponentCreator(session) + generate_temp_data() temp_files = os.listdir(TEMP_DIR_DATA_PATH) @@ -805,6 +944,22 @@ def main_window(selection): ) print("Shot entity is: {}".format(f_s_entity)) + # 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, @@ -834,7 +989,7 @@ def main_window(selection): task_entity.create_note(task_description, user) try: - session.commit() + session._commit() except Exception: tp, value, tb = sys.exc_info() session.rollback() From 77262990b4d98c61880cc422900e7cebc03dfc83 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 Nov 2021 07:52:49 +0100 Subject: [PATCH 31/58] debugging component creator --- openpype/hosts/flame/hooks/pre_flame_setup.py | 2 +- .../openpype_flame_to_ftrack.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 9043eb6cc6..718c4b574c 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -33,7 +33,7 @@ class FlamePrelaunch(PreLaunchHook): """Hook entry method.""" project_doc = self.data["project_doc"] user_name = get_openpype_username() - hostname = socket.gethostname() + hostname = socket.gethostname() # not returning wiretap host name self.log.debug("Collected user \"{}\"".format(user_name)) self.log.info(pformat(project_doc)) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index c12c1afb72..e19402914d 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -452,7 +452,8 @@ class FtrackComponentCreator: self.session = session self._get_ftrack_location() - def create_comonent(self, parent, data, assetversion_entity=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"] @@ -489,7 +490,7 @@ class FtrackComponentCreator: asset_entity = self._get_asset({ "name": "plateReference", "type": assettype_entity, - "parent": parent + "parent": self.shot_entity }) # get or create assetversion entity from session @@ -573,6 +574,7 @@ class FtrackComponentCreator: 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() @@ -595,7 +597,7 @@ class FtrackComponentCreator: def _get_assetversion(self, data): assetversion_entity = self.session.query( - self._query("AssetVersion", data) + self._query("AssetVersion", data) ).first() if assetversion_entity: @@ -610,7 +612,7 @@ class FtrackComponentCreator: def _commit(self): try: - self.session._commit() + self.session.commit() except Exception: tp, value, tb = sys.exc_info() self.session.rollback() @@ -618,6 +620,8 @@ class FtrackComponentCreator: 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] @@ -751,7 +755,7 @@ def main_window(selection): 'parent': parent }) try: - session._commit() + session.commit() except Exception: tp, value, tb = sys.exc_info() session.rollback() @@ -989,7 +993,7 @@ def main_window(selection): task_entity.create_note(task_description, user) try: - session._commit() + session.commit() except Exception: tp, value, tb = sys.exc_info() session.rollback() From f68d23b3f8db40dcb6b5f1c4da3cd7e980f0618d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 Nov 2021 11:00:07 +0100 Subject: [PATCH 32/58] breaking into multimodule --- .../flame_to_ftrack_modules/__init__.py | 0 .../flame_to_ftrack_modules/uiwidgets.py | 194 ++++++++++++++ .../openpype_flame_to_ftrack.py | 243 ++---------------- 3 files changed, 222 insertions(+), 215 deletions(-) create mode 100644 openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/__init__.py create mode 100644 openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/uiwidgets.py diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/__init__.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/uiwidgets.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/uiwidgets.py new file mode 100644 index 0000000000..c04801da6f --- /dev/null +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/uiwidgets.py @@ -0,0 +1,194 @@ +from PySide2 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"}' + 'QLabel:disabled {color: #6a6a6a}') + elif label_type == 'background': + self.setAlignment(QtCore.Qt.AlignCenter) + self.setStyleSheet( + 'color: #9a9a9a; background-color: #393939; font: 14px "Discreet"') + elif label_type == 'outline': + self.setAlignment(QtCore.Qt.AlignCenter) + self.setStyleSheet( + 'color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"') + + +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"}' + 'QLineEdit:focus {background-color: #474e58}' + '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"}' + 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' + 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' + 'QTreeWidget::item:selected {selection-background-color: #111111}' + 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' + '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"}' + 'QPushButton:pressed {color: #d9d9d9; background-color: #4f4f4f; border-top: 1px inset #666666; font: italic}' + 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') + + +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"}' + '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}' + '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}' + 'QToolTip {color: black; background-color: #ffffde; border: black solid 1px}') + + +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"}' + 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') + + pushbutton_menu = QtWidgets.QMenu(parent_window) + pushbutton_menu.setFocusPolicy(QtCore.Qt.NoFocus) + pushbutton_menu.setStyleSheet('QMenu {color: #9a9a9a; background-color:#24303d; font: 14px "Discreet"}' + '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()) \ No newline at end of file diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index e19402914d..d7daa28b95 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -21,10 +21,12 @@ TEMP_DIR_DATA_PATH = None F_PROJ_ENTITY = None SCRIPT_DIR = os.path.dirname(__file__) +PACKAGE_DIR = os.pah.join(SCRIPT_DIR, "flame_to_ftrack_modules") EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") CONFIG_DIR = os.path.join(os.path.expanduser( "~/.openpype"), "openpype_flame_to_ftrack") +sys.path.append(PACKAGE_DIR) def import_ftrack_api(): try: @@ -253,197 +255,6 @@ def timecode_to_frames(timecode, framerate): return frames - -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"}' - 'QLabel:disabled {color: #6a6a6a}') - elif label_type == 'background': - self.setAlignment(QtCore.Qt.AlignCenter) - self.setStyleSheet( - 'color: #9a9a9a; background-color: #393939; font: 14px "Discreet"') - elif label_type == 'outline': - self.setAlignment(QtCore.Qt.AlignCenter) - self.setStyleSheet( - 'color: #9a9a9a; background-color: #212121; border: 1px solid #404040; font: 14px "Discreet"') - - -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"}' - 'QLineEdit:focus {background-color: #474e58}' - '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"}' - 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' - 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' - 'QTreeWidget::item:selected {selection-background-color: #111111}' - 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' - '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"}' - 'QPushButton:pressed {color: #d9d9d9; background-color: #4f4f4f; border-top: 1px inset #666666; font: italic}' - 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') - - -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"}' - '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}' - '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}' - 'QToolTip {color: black; background-color: #ffffde; border: black solid 1px}') - - -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"}' - 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') - - pushbutton_menu = QtWidgets.QMenu(parent_window) - pushbutton_menu.setFocusPolicy(QtCore.Qt.NoFocus) - pushbutton_menu.setStyleSheet('QMenu {color: #9a9a9a; background-color:#24303d; font: 14px "Discreet"}' - '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()) - - class FtrackComponentCreator: default_location = "ftrack.server" ftrack_locations = {} @@ -676,6 +487,8 @@ class FtrackComponentCreator: def main_window(selection): import flame + import uiwidgets + global TEMP_DIR_DATA_PATH global F_PROJ_ENTITY @@ -1043,7 +856,7 @@ def main_window(selection): print(ordered_column_labels) - tree = FlameTreeWidget(ordered_column_labels, window) + tree = uiwidgets.FlameTreeWidget(ordered_column_labels, window) # Allow multiple items in tree to be selected tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) @@ -1067,40 +880,40 @@ def main_window(selection): break # input fields - shot_name_label = FlameLabel( + shot_name_label = uiwidgets.FlameLabel( 'Shot name template', 'normal', window) - shot_name_template_input = FlameLineEdit( + shot_name_template_input = uiwidgets.FlameLineEdit( cfg_d["shot_name_template"], window) - hierarchy_label = FlameLabel( + hierarchy_label = uiwidgets.FlameLabel( 'Parents template', 'normal', window) - hierarchy_template_input = FlameLineEdit( + hierarchy_template_input = uiwidgets.FlameLineEdit( cfg_d["hierarchy_template"], window) - start_frame_label = FlameLabel( + start_frame_label = uiwidgets.FlameLabel( 'Workfile start frame', 'normal', window) - start_frame_input = FlameLineEdit( + start_frame_input = uiwidgets.FlameLineEdit( cfg_d["workfile_start_frame"], window) - handles_label = FlameLabel( + handles_label = uiwidgets.FlameLabel( 'Shot handles', 'normal', window) - handles_input = FlameLineEdit(cfg_d["shot_handles"], window) + handles_input = uiwidgets.FlameLineEdit(cfg_d["shot_handles"], window) - width_label = FlameLabel( + width_label = uiwidgets.FlameLabel( 'Sequence width', 'normal', window) - width_input = FlameLineEdit(str(seq_width), window) + width_input = uiwidgets.FlameLineEdit(str(seq_width), window) - height_label = FlameLabel( + height_label = uiwidgets.FlameLabel( 'Sequence height', 'normal', window) - height_input = FlameLineEdit(str(seq_height), window) + height_input = uiwidgets.FlameLineEdit(str(seq_height), window) - pixel_aspect_label = FlameLabel( + pixel_aspect_label = uiwidgets.FlameLabel( 'Pixel aspect ratio', 'normal', window) - pixel_aspect_input = FlameLineEdit(str(1.00), window) + pixel_aspect_input = uiwidgets.FlameLineEdit(str(1.00), window) - fps_label = FlameLabel( + fps_label = uiwidgets.FlameLabel( 'Frame rate', 'normal', window) - fps_input = FlameLineEdit(str(fps), window) + fps_input = uiwidgets.FlameLineEdit(str(fps), window) # get project name from flame current project project_name = flame.project.current_project.name @@ -1121,25 +934,25 @@ def main_window(selection): project_names = [p["full_name"] for p in all_projects] TASK_TYPES_ALL = {p["full_name"]: get_all_task_types( p).keys() for p in all_projects} - project_select_label = FlameLabel( + project_select_label = uiwidgets.FlameLabel( 'Select Ftrack project', 'normal', window) - project_select_input = FlamePushButtonMenu( + project_select_input = uiwidgets.FlamePushButtonMenu( F_PROJ_ENTITY["full_name"], project_names, window) project_select_input.selection_changed.connect(_on_project_changed) F_PROJ_TASK_TYPES = get_all_task_types(F_PROJ_ENTITY) - task_type_label = FlameLabel( + task_type_label = uiwidgets.FlameLabel( 'Create Task (type)', 'normal', window) - task_type_input = FlamePushButtonMenu( + task_type_input = uiwidgets.FlamePushButtonMenu( cfg_d["create_task_type"], F_PROJ_TASK_TYPES.keys(), window) # Button - select_all_btn = FlameButton('Select All', select_all, window) - remove_temp_data_btn = FlameButton( + select_all_btn = uiwidgets.FlameButton('Select All', select_all, window) + remove_temp_data_btn = uiwidgets.FlameButton( 'Remove temp data', remove_temp_data, window) - ftrack_send_btn = FlameButton('Send to Ftrack', send_to_ftrack, window) + ftrack_send_btn = uiwidgets.FlameButton('Send to Ftrack', send_to_ftrack, window) # left props v_shift = 0 From 1fae4704f18f9a310fe9bea4a2996f9cef89ae8e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 Nov 2021 12:11:24 +0100 Subject: [PATCH 33/58] adding alter xml for export preset --- .../openpype_flame_to_ftrack.py | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index d7daa28b95..41ddb05046 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -28,6 +28,7 @@ CONFIG_DIR = os.path.join(os.path.expanduser( sys.path.append(PACKAGE_DIR) + def import_ftrack_api(): try: import ftrack_api @@ -208,27 +209,41 @@ def get_all_task_types(project_entity): return tasks +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) -def export_thumbnail(sequence, tempdir_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, export_preset, tempdir_path) + poster_frame_exporter.export(sequence, new_path, tempdir_path) -def export_video(sequence, 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, export_preset, tempdir_path) + poster_frame_exporter.export(sequence, new_path, tempdir_path) def timecode_to_frames(timecode, framerate): @@ -547,15 +562,15 @@ def main_window(selection): shutil.rmtree(TEMP_DIR_DATA_PATH) TEMP_DIR_DATA_PATH = None - def generate_temp_data(): + def generate_temp_data(change_preset_data): global TEMP_DIR_DATA_PATH if TEMP_DIR_DATA_PATH: return True with make_temp_dir() as tempdir_path: for seq in selection: - export_thumbnail(seq, tempdir_path) - export_video(seq, tempdir_path) + export_thumbnail(seq, tempdir_path, change_preset_data) + export_video(seq, tempdir_path, change_preset_data) TEMP_DIR_DATA_PATH = tempdir_path break @@ -687,7 +702,9 @@ def main_window(selection): component_creator = FtrackComponentCreator(session) - generate_temp_data() + generate_temp_data({ + "nbHandles": handles + }) temp_files = os.listdir(TEMP_DIR_DATA_PATH) thumbnails = [f for f in temp_files if "jpg" in f] From 2e3c3e78aee2a097f85bb0abb77d89c873cdfd5e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 Nov 2021 12:15:20 +0100 Subject: [PATCH 34/58] fix on updating metadata of ftrack componet --- .../openpype_flame_to_ftrack/openpype_flame_to_ftrack.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 41ddb05046..8e51320b00 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -380,6 +380,11 @@ class FtrackComponentCreator: ).first() if component_entity: + # Adding metadata + existing_component_metadata = component_entity["metadata"] + existing_component_metadata.update(component_metadata) + component_entity["metadata"] = existing_component_metadata + # overwrite existing members in component enity # - get data for member from `ftrack.origin` location self._overwrite_members(component_entity, comp_data) From 04523a2e94ce8e512a398a2fbf1d85c0fffbd77b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 Nov 2021 13:27:59 +0100 Subject: [PATCH 35/58] fix typo --- .../openpype_flame_to_ftrack/openpype_flame_to_ftrack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 8e51320b00..0501c24b48 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -21,7 +21,7 @@ TEMP_DIR_DATA_PATH = None F_PROJ_ENTITY = None SCRIPT_DIR = os.path.dirname(__file__) -PACKAGE_DIR = os.pah.join(SCRIPT_DIR, "flame_to_ftrack_modules") +PACKAGE_DIR = os.path.join(SCRIPT_DIR, "flame_to_ftrack_modules") EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") CONFIG_DIR = os.path.join(os.path.expanduser( "~/.openpype"), "openpype_flame_to_ftrack") From 835419d839351c954c6fc96b2a9336568663c490 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 9 Nov 2021 11:00:22 +0100 Subject: [PATCH 36/58] little improvements --- .../openpype_flame_to_ftrack.py | 88 ++++++++++++------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 0501c24b48..a598c52cc0 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -1,6 +1,5 @@ from __future__ import print_function import os -import six import sys import re import json @@ -19,6 +18,7 @@ FTRACK_SERVER = None TEMP_DIR_DATA_PATH = None F_PROJ_ENTITY = None +COMPONENTS_DONE = [] SCRIPT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.join(SCRIPT_DIR, "flame_to_ftrack_modules") @@ -209,6 +209,7 @@ def get_all_task_types(project_entity): return tasks + def configure_preset(file_path, data): split_fp = os.path.splitext(file_path) new_file_path = split_fp[0] + "_tmp" + split_fp[-1] @@ -222,6 +223,7 @@ def configure_preset(file_path, data): return new_file_path + def export_thumbnail(sequence, tempdir_path, data): import flame export_preset = os.path.join( @@ -270,6 +272,7 @@ def timecode_to_frames(timecode, framerate): return frames + class FtrackComponentCreator: default_location = "ftrack.server" ftrack_locations = {} @@ -278,6 +281,10 @@ class FtrackComponentCreator: self.session = session self._get_ftrack_location() + 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() @@ -304,7 +311,7 @@ class FtrackComponentCreator: component_data.update({ "metadata": {'ftr_meta': json.dumps({ 'frameIn': int(0), - 'frameOut': int(duration + handles), + 'frameOut': int(duration + (handles * 2)), 'frameRate': float(fps)})} }) @@ -380,14 +387,14 @@ class FtrackComponentCreator: ).first() if component_entity: + # overwrite existing members in component enity + # - 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 - - # overwrite existing members in component enity - # - get data for member from `ftrack.origin` location - self._overwrite_members(component_entity, comp_data) return assetversion_entity = base_data["version"] @@ -511,6 +518,7 @@ def main_window(selection): global TEMP_DIR_DATA_PATH global F_PROJ_ENTITY + global COMPONENTS_DONE def _on_project_changed(project_name): task_types = TASK_TYPES_ALL[project_name] @@ -524,6 +532,8 @@ def main_window(selection): for tracks in ver.tracks: for segment in tracks.segments: print(segment.attributes) + if str(segment.name)[1:-1] == "": + continue # get clip frame duration record_duration = str(segment.record_duration)[1:-1] clip_duration = timecode_to_frames( @@ -561,12 +571,17 @@ def main_window(selection): tree.selectAll() def remove_temp_data(): - global TEMP_DIR_DATA_PATH import shutil + global TEMP_DIR_DATA_PATH + global COMPONENTS_DONE + + COMPONENTS_DONE = [] + if TEMP_DIR_DATA_PATH: shutil.rmtree(TEMP_DIR_DATA_PATH) TEMP_DIR_DATA_PATH = None + def generate_temp_data(change_preset_data): global TEMP_DIR_DATA_PATH if TEMP_DIR_DATA_PATH: @@ -707,9 +722,10 @@ def main_window(selection): component_creator = FtrackComponentCreator(session) + generate_temp_data({ - "nbHandles": handles - }) + "nbHandles": handles + }) temp_files = os.listdir(TEMP_DIR_DATA_PATH) thumbnails = [f for f in temp_files if "jpg" in f] @@ -736,15 +752,21 @@ def main_window(selection): # get component files thumb_f = next((f for f in thumbnails if shot_name in f), None) video_f = next((f for f in videos if shot_name in f), None) - print(thumb_f) - print(video_f) - thumb_fp = os.path.join(TEMP_DIR_DATA_PATH, thumb_f) video_fp = os.path.join(TEMP_DIR_DATA_PATH, video_f) print(thumb_fp) print(video_fp) - # populate full shot info + print("processed comps: {}".format(COMPONENTS_DONE)) + processed = False + if thumb_f not in COMPONENTS_DONE: + COMPONENTS_DONE.append(thumb_f) + else: + processed = True + + print("processed: {}".format(processed)) + + # populate full shot info shot_attributes = { "sequence": sequence_name, "shot": shot_name, @@ -783,21 +805,23 @@ def main_window(selection): ) print("Shot entity is: {}".format(f_s_entity)) - # 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 - ) + 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 = { @@ -835,6 +859,8 @@ def main_window(selection): session._configure_locations() six.reraise(tp, value, tb) + component_creator.close() + # creating ui window = QtWidgets.QWidget() window.setMinimumSize(1500, 600) @@ -970,11 +996,13 @@ def main_window(selection): cfg_d["create_task_type"], F_PROJ_TASK_TYPES.keys(), window) # Button - select_all_btn = uiwidgets.FlameButton('Select All', select_all, window) + select_all_btn = uiwidgets.FlameButton( + 'Select All', select_all, window) remove_temp_data_btn = uiwidgets.FlameButton( 'Remove temp data', remove_temp_data, window) - ftrack_send_btn = uiwidgets.FlameButton('Send to Ftrack', send_to_ftrack, window) + ftrack_send_btn = uiwidgets.FlameButton( + 'Send to Ftrack', send_to_ftrack, window) # left props v_shift = 0 From 4c57c117dfd0ddd15708d6c6f5fe0ba68654e290 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 18 Nov 2021 11:21:17 +0100 Subject: [PATCH 37/58] rename folder to simple modules --- .../{flame_to_ftrack_modules => modules}/__init__.py | 0 .../{flame_to_ftrack_modules => modules}/uiwidgets.py | 0 .../openpype_flame_to_ftrack/openpype_flame_to_ftrack.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/{flame_to_ftrack_modules => modules}/__init__.py (100%) rename openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/{flame_to_ftrack_modules => modules}/uiwidgets.py (100%) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/__init__.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/__init__.py similarity index 100% rename from openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/__init__.py rename to openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/__init__.py diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/uiwidgets.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/uiwidgets.py similarity index 100% rename from openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/flame_to_ftrack_modules/uiwidgets.py rename to openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/uiwidgets.py diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index a598c52cc0..daab7c1754 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -21,7 +21,7 @@ F_PROJ_ENTITY = None COMPONENTS_DONE = [] SCRIPT_DIR = os.path.dirname(__file__) -PACKAGE_DIR = os.path.join(SCRIPT_DIR, "flame_to_ftrack_modules") +PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") CONFIG_DIR = os.path.join(os.path.expanduser( "~/.openpype"), "openpype_flame_to_ftrack") From 9c60961a61e97a5a5d2bd449c06ccc84ec7aba76 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Nov 2021 15:57:10 +0100 Subject: [PATCH 38/58] converting to class based code --- .../openpype_flame_to_ftrack/modules/app.py | 513 ++++++++ .../modules/ftrack_lib.py | 420 +++++++ .../openpype_flame_to_ftrack/modules/utils.py | 174 +++ .../openpype_flame_to_ftrack.py | 1055 ----------------- 4 files changed, 1107 insertions(+), 1055 deletions(-) create mode 100644 openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py create mode 100644 openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py create mode 100644 openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/utils.py diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py new file mode 100644 index 0000000000..a13df25035 --- /dev/null +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py @@ -0,0 +1,513 @@ +import os +from PySide2 import QtWidgets, QtCore +import uiwidgets +import flame + +from .ftrack_lib import ( + maintained_ftrack_session, + FtrackEntityOperator, + FtrackComponentCreator +) +from .utils import ( + get_config, + set_config, + get_all_task_types, + make_temp_dir, + export_thumbnail, + export_video, + timecode_to_frames +) +from pprint import pformat + + +class FlameToFtrackPanel(QtWidgets.QWidget()): + temp_data_dir = None + project_entity = None + task_types = {} + all_task_types = {} + processed_components = [] + + # 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, *args, **kwargs): + super(FlameToFtrackPanel, self).__init__(*args, **kwargs) + + self.selection = selection + # creating ui + self.setMinimumSize(1500, 600) + self.setWindowTitle('Sequence Shots to Ftrack') + self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setStyleSheet('background-color: #313131') + + + self._create_tree_widget() + self._set_sequence_params() + + self._create_project_widget() + + self._generate_widgets() + + self._generate_layouts() + + self._timeline_info() + + self._fix_resolution() + + def _generate_widgets(self): + with get_config("main") as cfg_d: + self._create_task_type_widget(cfg_d) + + # input fields + self.shot_name_label = uiwidgets.FlameLabel( + 'Shot name template', 'normal', self) + self.shot_name_template_input = uiwidgets.FlameLineEdit( + cfg_d["shot_name_template"], self) + + self.hierarchy_label = uiwidgets.FlameLabel( + 'Parents template', 'normal', self) + self.hierarchy_template_input = uiwidgets.FlameLineEdit( + cfg_d["hierarchy_template"], self) + + self.start_frame_label = uiwidgets.FlameLabel( + 'Workfile start frame', 'normal', self) + self.start_frame_input = uiwidgets.FlameLineEdit( + cfg_d["workfile_start_frame"], self) + + self.handles_label = uiwidgets.FlameLabel( + 'Shot handles', 'normal', self) + self.handles_input = uiwidgets.FlameLineEdit( + cfg_d["shot_handles"], self) + + self.width_label = uiwidgets.FlameLabel( + 'Sequence width', 'normal', self) + self.width_input = uiwidgets.FlameLineEdit( + str(self.seq_width), self) + + self.height_label = uiwidgets.FlameLabel( + 'Sequence height', 'normal', self) + self.height_input = uiwidgets.FlameLineEdit( + str(self.seq_height), self) + + self.pixel_aspect_label = uiwidgets.FlameLabel( + 'Pixel aspect ratio', 'normal', self) + self.pixel_aspect_input = uiwidgets.FlameLineEdit( + str(1.00), self) + + self.fps_label = uiwidgets.FlameLabel( + 'Frame rate', 'normal', self) + self.fps_input = uiwidgets.FlameLineEdit( + str(self.fps), self) + + # Button + self.select_all_btn = uiwidgets.FlameButton( + 'Select All', self.select_all, self) + + self.remove_temp_data_btn = uiwidgets.FlameButton( + 'Remove temp data', self.remove_temp_data, self) + + self.ftrack_send_btn = uiwidgets.FlameButton( + 'Send to Ftrack', self._send_to_ftrack, self) + + 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) + 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) + 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): + self.task_types = get_all_task_types(self.project_entity) + + self.task_type_label = uiwidgets.FlameLabel( + 'Create Task (type)', 'normal', self) + self.task_type_input = uiwidgets.FlamePushButtonMenu( + cfg_d["create_task_type"], self.task_types.keys(), self) + + def _create_project_widget(self): + + with maintained_ftrack_session() as session: + # 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 = session.query(query).first() + + self.project_selector_enabled = bool(not self.project_entity) + + if self.project_selector_enabled: + self.all_projects = 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"]: get_all_task_types( + p).keys() for p in self.all_projects} + self.project_select_label = uiwidgets.FlameLabel( + 'Select Ftrack project', 'normal', self) + self.project_select_input = uiwidgets.FlamePushButtonMenu( + self.project_entity["full_name"], project_names, self) + 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) + + # 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 _send_to_ftrack(self): + # resolve active project and add it to self.project_entity + 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 + ) + + _cfg_data_back = {} + + # get shot name template from gui input + shot_name_template = self.shot_name_template_input.text() + + # get hierarchy from gui input + hierarchy_text = self.hierarchy_template_input.text() + + # get hanldes 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 + width = self.width_input.text() + height = self.height_input.text() + pixel_aspect = self.pixel_aspect_input.text() + fps = self.fps_input.text() + + _cfg_data_back = { + "shot_name_template": shot_name_template, + "workfile_start_frame": str(frame_start), + "shot_handles": handles, + "hierarchy_template": hierarchy_text, + "create_task_type": task_type + } + + # add cfg data back to settings.ini + set_config(_cfg_data_back, "main") + + with maintained_ftrack_session() as session: + print("Ftrack session is: {}".format(session)) + + entity_operator = FtrackEntityOperator( + session, self.project_entity) + component_creator = FtrackComponentCreator(session) + + self.generate_temp_data({ + "nbHandles": handles + }) + + temp_files = os.listdir(self.temp_data_dir) + thumbnails = [f for f in temp_files if "jpg" in f] + videos = [f for f in temp_files if "mov" in f] + + print(temp_files) + print(thumbnails) + print(videos) + + # 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) + + # get component files + thumb_f = next((f for f in thumbnails if shot_name in f), None) + video_f = next((f for f in videos if shot_name in f), None) + thumb_fp = os.path.join(self.temp_data_dir, thumb_f) + video_fp = os.path.join(self.temp_data_dir, video_f) + print(thumb_fp) + print(video_fp) + + print("processed comps: {}".format(self.processed_components)) + processed = False + if thumb_f not in self.processed_components: + self.processed_components.append(thumb_f) + 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 = shot_name_template.format(**shot_attributes) + + # format hierarchy template + _hierarchy_text = hierarchy_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( + session, + _type, + _name, + _parent + ) + print(p_entity) + _parent = p_entity + + # obtain shot ftrack entity + f_s_entity = entity_operator.get_ftrack_entity( + 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(width), + "resolutionHeight": int(height), + "pixelAspect": float(pixel_aspect), + "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 = session.query( + "User where username is \"{}\"".format(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.move( + (resolution.width() / 2) - (self.frameSize().width() / 2), + (resolution.height() / 2) - (self.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 tracks in ver.tracks: + for segment in tracks.segments: + print(segment.attributes) + if str(segment.name)[1:-1] == "": + continue + # get clip frame duration + record_duration = str(segment.record_duration)[1:-1] + clip_duration = 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, [ + str(sequence.name)[1:-1], # seq + str(segment.name)[1:-1], # shot + str(clip_duration), # clip duration + shot_description, # shot description + str(segment.comment)[1:-1] # 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 remove_temp_data(self, ): + import shutil + + if self.temp_data_dir: + shutil.rmtree(self.temp_data_dir) + self.temp_data_dir = None + + def generate_temp_data(self, change_preset_data): + if self.temp_data_dir: + return True + + with make_temp_dir() as tempdir_path: + for seq in self.selection: + export_thumbnail(seq, tempdir_path, change_preset_data) + export_video(seq, tempdir_path, change_preset_data) + self.temp_data_dir = tempdir_path + break diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py new file mode 100644 index 0000000000..8ea1cfc775 --- /dev/null +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -0,0 +1,420 @@ +import os +import sys +import six +import re +from contextlib import contextmanager + +# 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 + + +@contextmanager +def maintained_ftrack_session(): + import os + ftrack_api = import_ftrack_api() + + def validate_credentials(url, user, api): + 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: + session = ftrack_api.Session( + server_url=url, + api_user=user, + api_key=api + ) + session.close() + 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 + print( + 'Credentials Username: "{}", API key: "{}" are valid.'.format( + user, api) + ) + return True + + # 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 "" + + try: + assert validate_credentials(url, user, api), ( + "Ftrack credentials failed") + # open ftrack session + session = ftrack_api.Session( + server_url=url, + api_user=user, + api_key=api + ) + yield session + except Exception: + tp, value, tb = sys.exc_info() + six.reraise(tp, value, tb) + finally: + # close the session + session.close() + + +class FtrackComponentCreator: + default_location = "ftrack.server" + ftrack_locations = {} + + def __init__(self, session): + self.session = session + self._get_ftrack_location() + + 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.update({ + "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": 1, + "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") + + # Removing existing members from location + components = list(entity.get("members", [])) + components += [entity] + for component in components: + for loc in component["component_locations"]: + if location["id"] == loc["location_id"]: + location.remove_component( + component, recursive=False + ) + + # Deleting existing members on component entity + for member in entity.get("members", []): + self.session.delete(member) + del(member) + + self._commit() + + # Reset members in memory + if "members" in entity.keys(): + entity["members"] = [] + + entity["file_type"] = data["file_type"] + + origin_location.add_component( + entity, data["file_path"] + ) + + # Add components to location. + location.add_component( + entity, origin_location, recursive=True) + + 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 enity + # - 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.iteritems(): + if not isinstance(value, (basestring, 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: + 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"]) + + try: + entity = session.query(query).one() + except Exception: + entity = None + + # if entity doesnt 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): + existing_task = [ + child for child in parent['children'] + if child.entity_type.lower() == 'task' + if child['name'].lower() in task_type.lower() + ] + + if existing_task: + return existing_task.pop() + + task = self.session.create('Task', { + "name": task_type.lower(), + "parent": parent + }) + task["type"] = task_types[task_type] + + return task diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/utils.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/utils.py new file mode 100644 index 0000000000..2aa6577325 --- /dev/null +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/utils.py @@ -0,0 +1,174 @@ +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_flame_to_ftrack") + + +@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 get_all_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 + + +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/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index daab7c1754..bfdaf75385 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -1,1070 +1,15 @@ from __future__ import print_function import os import sys -import re import json -from PySide2 import QtWidgets, QtCore -from pprint import pformat -from contextlib import contextmanager -from xml.etree import ElementTree as ET -import ConfigParser as CP -import io -# Fill following constants or set them via environment variable -FTRACK_MODULE_PATH = None -FTRACK_API_KEY = None -FTRACK_API_USER = None -FTRACK_SERVER = None - -TEMP_DIR_DATA_PATH = None -F_PROJ_ENTITY = None -COMPONENTS_DONE = [] SCRIPT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") -EXPORT_PRESETS_DIR = os.path.join(SCRIPT_DIR, "export_preset") -CONFIG_DIR = os.path.join(os.path.expanduser( - "~/.openpype"), "openpype_flame_to_ftrack") sys.path.append(PACKAGE_DIR) -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 - - -@contextmanager -def maintained_ftrack_session(): - import os - ftrack_api = import_ftrack_api() - - def validate_credentials(url, user, api): - 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: - session = ftrack_api.Session( - server_url=url, - api_user=user, - api_key=api - ) - session.close() - 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 - print( - 'Credentials Username: "{}", API key: "{}" are valid.'.format( - user, api) - ) - return True - - # 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 "" - - try: - assert validate_credentials(url, user, api), ( - "Ftrack credentials failed") - # open ftrack session - session = ftrack_api.Session( - server_url=url, - api_user=user, - api_key=api - ) - yield session - except Exception: - tp, value, tb = sys.exc_info() - six.reraise(tp, value, tb) - finally: - # close the session - session.close() - - -@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 get_all_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 - - -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 - - -class FtrackComponentCreator: - default_location = "ftrack.server" - ftrack_locations = {} - - def __init__(self, session): - self.session = session - self._get_ftrack_location() - - 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.update({ - "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": 1, - "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") - - # Removing existing members from location - components = list(entity.get("members", [])) - components += [entity] - for component in components: - for loc in component["component_locations"]: - if location["id"] == loc["location_id"]: - location.remove_component( - component, recursive=False - ) - - # Deleting existing members on component entity - for member in entity.get("members", []): - self.session.delete(member) - del(member) - - self._commit() - - # Reset members in memory - if "members" in entity.keys(): - entity["members"] = [] - - entity["file_type"] = data["file_type"] - - origin_location.add_component( - entity, data["file_path"] - ) - - # Add components to location. - location.add_component( - entity, origin_location, recursive=True) - - 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 enity - # - 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.iteritems(): - if not isinstance(value, (basestring, 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 - - -def main_window(selection): - import flame - import uiwidgets - - global TEMP_DIR_DATA_PATH - global F_PROJ_ENTITY - global COMPONENTS_DONE - - def _on_project_changed(project_name): - task_types = TASK_TYPES_ALL[project_name] - task_type_input.set_menu_options(task_types) - - def timeline_info(selection): - # identificar as informacoes dos segmentos na timeline - for sequence in selection: - frame_rate = float(str(sequence.frame_rate)[:-4]) - for ver in sequence.versions: - for tracks in ver.tracks: - for segment in tracks.segments: - print(segment.attributes) - if str(segment.name)[1:-1] == "": - continue - # get clip frame duration - record_duration = str(segment.record_duration)[1:-1] - clip_duration = 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(tree, [ - str(sequence.name)[1:-1], # seq - str(segment.name)[1:-1], # shot - str(clip_duration), # clip duration - shot_description, # shot description - str(segment.comment)[1:-1] # task description - ]).setFlags( - QtCore.Qt.ItemIsEditable - | QtCore.Qt.ItemIsEnabled - | QtCore.Qt.ItemIsSelectable - ) - - # Select top item in tree - tree.setCurrentItem(tree.topLevelItem(0)) - - def select_all(): - - tree.selectAll() - - def remove_temp_data(): - import shutil - global TEMP_DIR_DATA_PATH - global COMPONENTS_DONE - - COMPONENTS_DONE = [] - - if TEMP_DIR_DATA_PATH: - shutil.rmtree(TEMP_DIR_DATA_PATH) - TEMP_DIR_DATA_PATH = None - - - def generate_temp_data(change_preset_data): - global TEMP_DIR_DATA_PATH - if TEMP_DIR_DATA_PATH: - return True - - with make_temp_dir() as tempdir_path: - for seq in selection: - export_thumbnail(seq, tempdir_path, change_preset_data) - export_video(seq, tempdir_path, change_preset_data) - TEMP_DIR_DATA_PATH = tempdir_path - break - - def send_to_ftrack(): - def create_ftrack_entity(session, type, name, parent=None): - global F_PROJ_ENTITY - parent = parent or F_PROJ_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(session, type, name, parent): - global F_PROJ_ENTITY - query = '{} where name is "{}" and project_id is "{}"'.format( - type, name, F_PROJ_ENTITY["id"]) - - try: - entity = session.query(query).one() - except Exception: - entity = None - - # if entity doesnt exist then create one - if not entity: - entity = create_ftrack_entity( - session, - type, - name, - parent - ) - - return entity - - def generate_parents_from_template(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(task_type, parent): - existing_task = [ - child for child in parent['children'] - if child.entity_type.lower() == 'task' - if child['name'].lower() in task_type.lower() - ] - - if existing_task: - return existing_task.pop() - - task = session.create('Task', { - "name": task_type.lower(), - "parent": parent - }) - task["type"] = F_PROJ_TASK_TYPES[task_type] - - return task - - ''' - ##################### start procedure - ''' - # resolve active project and add it to F_PROJ_ENTITY - if proj_selector: - selected_project_name = project_select_input.text() - F_PROJ_ENTITY = next( - (p for p in all_projects - if p["full_name"] in selected_project_name), - None - ) - - _cfg_data_back = {} - - # get shot name template from gui input - shot_name_template = shot_name_template_input.text() - - # get hierarchy from gui input - hierarchy_text = hierarchy_template_input.text() - - # get hanldes from gui input - handles = handles_input.text() - - # get frame start from gui input - frame_start = int(start_frame_input.text()) - - # get task type from gui input - task_type = task_type_input.text() - - # get resolution from gui inputs - width = width_input.text() - height = height_input.text() - pixel_aspect = pixel_aspect_input.text() - fps = fps_input.text() - - _cfg_data_back = { - "shot_name_template": shot_name_template, - "workfile_start_frame": str(frame_start), - "shot_handles": handles, - "hierarchy_template": hierarchy_text, - "create_task_type": task_type - } - - # add cfg data back to settings.ini - set_config(_cfg_data_back, "main") - - with maintained_ftrack_session() as session: - print("Ftrack session is: {}".format(session)) - - component_creator = FtrackComponentCreator(session) - - - generate_temp_data({ - "nbHandles": handles - }) - - temp_files = os.listdir(TEMP_DIR_DATA_PATH) - thumbnails = [f for f in temp_files if "jpg" in f] - videos = [f for f in temp_files if "mov" in f] - - print(temp_files) - print(thumbnails) - print(videos) - - # Get all selected items from treewidget - for item in 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) - - # get component files - thumb_f = next((f for f in thumbnails if shot_name in f), None) - video_f = next((f for f in videos if shot_name in f), None) - thumb_fp = os.path.join(TEMP_DIR_DATA_PATH, thumb_f) - video_fp = os.path.join(TEMP_DIR_DATA_PATH, video_f) - print(thumb_fp) - print(video_fp) - - print("processed comps: {}".format(COMPONENTS_DONE)) - processed = False - if thumb_f not in COMPONENTS_DONE: - COMPONENTS_DONE.append(thumb_f) - 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 = shot_name_template.format(**shot_attributes) - - # format hierarchy template - _hierarchy_text = hierarchy_text.format(**shot_attributes) - print(_hierarchy_text) - - # solve parents - parents = generate_parents_from_template(_hierarchy_text) - print(parents) - - # obtain shot parents entities - _parent = None - for _name, _type in parents: - p_entity = get_ftrack_entity( - session, - _type, - _name, - _parent - ) - print(p_entity) - _parent = p_entity - - # obtain shot ftrack entity - f_s_entity = get_ftrack_entity( - 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(width), - "resolutionHeight": int(height), - "pixelAspect": float(pixel_aspect), - "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 = create_task(task_type, f_s_entity) - - # Create notes. - user = session.query( - "User where username is \"{}\"".format(session.api_user) - ).first() - - f_s_entity.create_note(shot_description, author=user) - - if task_description: - task_entity.create_note(task_description, user) - - try: - session.commit() - except Exception: - tp, value, tb = sys.exc_info() - session.rollback() - session._configure_locations() - six.reraise(tp, value, tb) - - component_creator.close() - - # creating ui - window = QtWidgets.QWidget() - window.setMinimumSize(1500, 600) - window.setWindowTitle('Sequence Shots to Ftrack') - window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - window.setAttribute(QtCore.Qt.WA_DeleteOnClose) - window.setStyleSheet('background-color: #313131') - - # Center window in linux - resolution = QtWidgets.QDesktopWidget().screenGeometry() - window.move((resolution.width() / 2) - (window.frameSize().width() / 2), - (resolution.height() / 2) - (window.frameSize().height() / 2)) - - # 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 - }, - } - ordered_column_labels = columns.keys() - for _name, _value in columns.items(): - ordered_column_labels.pop(_value["order"]) - ordered_column_labels.insert(_value["order"], _name) - - print(ordered_column_labels) - - tree = uiwidgets.FlameTreeWidget(ordered_column_labels, window) - - # Allow multiple items in tree to be selected - tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) - - # Set tree column width - for _name, _val in columns.items(): - tree.setColumnWidth( - _val["order"], - _val["columnWidth"] - ) - - # Prevent weird characters when shrinking tree columns - tree.setTextElideMode(QtCore.Qt.ElideNone) - - with maintained_ftrack_session() as _session, get_config("main") as cfg_d: - - for select in selection: - seq_height = select.height - seq_width = select.width - fps = float(str(select.frame_rate)[:-4]) - break - - # input fields - shot_name_label = uiwidgets.FlameLabel( - 'Shot name template', 'normal', window) - shot_name_template_input = uiwidgets.FlameLineEdit( - cfg_d["shot_name_template"], window) - - hierarchy_label = uiwidgets.FlameLabel( - 'Parents template', 'normal', window) - hierarchy_template_input = uiwidgets.FlameLineEdit( - cfg_d["hierarchy_template"], window) - - start_frame_label = uiwidgets.FlameLabel( - 'Workfile start frame', 'normal', window) - start_frame_input = uiwidgets.FlameLineEdit( - cfg_d["workfile_start_frame"], window) - - handles_label = uiwidgets.FlameLabel( - 'Shot handles', 'normal', window) - handles_input = uiwidgets.FlameLineEdit(cfg_d["shot_handles"], window) - - width_label = uiwidgets.FlameLabel( - 'Sequence width', 'normal', window) - width_input = uiwidgets.FlameLineEdit(str(seq_width), window) - - height_label = uiwidgets.FlameLabel( - 'Sequence height', 'normal', window) - height_input = uiwidgets.FlameLineEdit(str(seq_height), window) - - pixel_aspect_label = uiwidgets.FlameLabel( - 'Pixel aspect ratio', 'normal', window) - pixel_aspect_input = uiwidgets.FlameLineEdit(str(1.00), window) - - fps_label = uiwidgets.FlameLabel( - 'Frame rate', 'normal', window) - fps_input = uiwidgets.FlameLineEdit(str(fps), window) - - # get project name from flame current project - 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(project_name) - - # globally used variables - F_PROJ_ENTITY = _session.query(query).first() - - proj_selector = bool(not F_PROJ_ENTITY) - - if proj_selector: - all_projects = _session.query( - "Project where status is active").all() - F_PROJ_ENTITY = all_projects[0] - project_names = [p["full_name"] for p in all_projects] - TASK_TYPES_ALL = {p["full_name"]: get_all_task_types( - p).keys() for p in all_projects} - project_select_label = uiwidgets.FlameLabel( - 'Select Ftrack project', 'normal', window) - project_select_input = uiwidgets.FlamePushButtonMenu( - F_PROJ_ENTITY["full_name"], project_names, window) - project_select_input.selection_changed.connect(_on_project_changed) - - F_PROJ_TASK_TYPES = get_all_task_types(F_PROJ_ENTITY) - - task_type_label = uiwidgets.FlameLabel( - 'Create Task (type)', 'normal', window) - task_type_input = uiwidgets.FlamePushButtonMenu( - cfg_d["create_task_type"], F_PROJ_TASK_TYPES.keys(), window) - - # Button - select_all_btn = uiwidgets.FlameButton( - 'Select All', select_all, window) - remove_temp_data_btn = uiwidgets.FlameButton( - 'Remove temp data', remove_temp_data, window) - - ftrack_send_btn = uiwidgets.FlameButton( - 'Send to Ftrack', send_to_ftrack, window) - - # left props - v_shift = 0 - prop_layout_l = QtWidgets.QGridLayout() - prop_layout_l.setHorizontalSpacing(30) - if proj_selector: - prop_layout_l.addWidget(project_select_label, v_shift, 0) - prop_layout_l.addWidget(project_select_input, v_shift, 1) - v_shift += 1 - prop_layout_l.addWidget(shot_name_label, (v_shift + 0), 0) - prop_layout_l.addWidget(shot_name_template_input, (v_shift + 0), 1) - prop_layout_l.addWidget(hierarchy_label, (v_shift + 1), 0) - prop_layout_l.addWidget(hierarchy_template_input, (v_shift + 1), 1) - prop_layout_l.addWidget(start_frame_label, (v_shift + 2), 0) - prop_layout_l.addWidget(start_frame_input, (v_shift + 2), 1) - prop_layout_l.addWidget(handles_label, (v_shift + 3), 0) - prop_layout_l.addWidget(handles_input, (v_shift + 3), 1) - prop_layout_l.addWidget(task_type_label, (v_shift + 4), 0) - prop_layout_l.addWidget(task_type_input, (v_shift + 4), 1) - - # right props - prop_widget_r = QtWidgets.QWidget(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(width_label, 1, 0) - prop_layout_r.addWidget(width_input, 1, 1) - prop_layout_r.addWidget(height_label, 2, 0) - prop_layout_r.addWidget(height_input, 2, 1) - prop_layout_r.addWidget(pixel_aspect_label, 3, 0) - prop_layout_r.addWidget(pixel_aspect_input, 3, 1) - prop_layout_r.addWidget(fps_label, 4, 0) - prop_layout_r.addWidget(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(remove_temp_data_btn) - hbox.addWidget(select_all_btn) - hbox.addWidget(ftrack_send_btn) - - # put all layouts together - main_frame = QtWidgets.QVBoxLayout(window) - main_frame.setMargin(20) - main_frame.addLayout(prop_main_layout) - main_frame.addWidget(tree) - main_frame.addLayout(hbox) - - window.show() - - timeline_info(selection) - - return window - - def scope_sequence(selection): import flame return any(isinstance(item, flame.PySequence) for item in selection) From dd1610a29cfa7763387cf219f4f8ff56a8be0d7e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Nov 2021 16:15:43 +0100 Subject: [PATCH 39/58] reducing redundant code --- .../openpype_flame_to_ftrack/modules/app.py | 49 +++++++++---------- .../openpype_flame_to_ftrack.py | 16 +++--- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py index a13df25035..6a654792f5 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py @@ -17,7 +17,6 @@ from .utils import ( export_video, timecode_to_frames ) -from pprint import pformat class FlameToFtrackPanel(QtWidgets.QWidget()): @@ -254,8 +253,7 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): # Prevent weird characters when shrinking tree columns self.tree.setTextElideMode(QtCore.Qt.ElideNone) - def _send_to_ftrack(self): - # resolve active project and add it to self.project_entity + def _resolve_project_entity(self): if self.project_selector_enabled: selected_project_name = self.project_select_input.text() self.project_entity = next( @@ -264,13 +262,22 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): None ) - _cfg_data_back = {} + 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() + } - # get shot name template from gui input - shot_name_template = self.shot_name_template_input.text() + # add cfg data back to settings.ini + set_config(_cfg_data_back, "main") - # get hierarchy from gui input - hierarchy_text = self.hierarchy_template_input.text() + 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 hanldes from gui input handles = self.handles_input.text() @@ -282,22 +289,8 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): task_type = self.task_type_input.text() # get resolution from gui inputs - width = self.width_input.text() - height = self.height_input.text() - pixel_aspect = self.pixel_aspect_input.text() fps = self.fps_input.text() - _cfg_data_back = { - "shot_name_template": shot_name_template, - "workfile_start_frame": str(frame_start), - "shot_handles": handles, - "hierarchy_template": hierarchy_text, - "create_task_type": task_type - } - - # add cfg data back to settings.ini - set_config(_cfg_data_back, "main") - with maintained_ftrack_session() as session: print("Ftrack session is: {}".format(session)) @@ -356,10 +349,12 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): } # format shot name template - _shot_name = shot_name_template.format(**shot_attributes) + _shot_name = self.shot_name_template_input.text().format( + **shot_attributes) # format hierarchy template - _hierarchy_text = hierarchy_text.format(**shot_attributes) + _hierarchy_text = self.hierarchy_template_input.text().format( + **shot_attributes) print(_hierarchy_text) # solve parents @@ -411,9 +406,9 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): "frameEnd": frame_end, "handleStart": int(handles), "handleEnd": int(handles), - "resolutionWidth": int(width), - "resolutionHeight": int(height), - "pixelAspect": float(pixel_aspect), + "resolutionWidth": int(self.width_input.text()), + "resolutionHeight": int(self.height_input.text()), + "pixelAspect": float(self.pixel_aspect_input.text()), "fps": float(fps) } diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index bfdaf75385..96828ae971 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -1,14 +1,16 @@ from __future__ import print_function + import os import sys -import json +try: + from app import FlameToFtrackPanel +except ImportError: + SCRIPT_DIR = os.path.dirname(__file__) + PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") + sys.path.append(PACKAGE_DIR) -SCRIPT_DIR = os.path.dirname(__file__) -PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") - -sys.path.append(PACKAGE_DIR) - + from app import FlameToFtrackPanel def scope_sequence(selection): import flame @@ -23,7 +25,7 @@ def get_media_panel_custom_ui_actions(): { "name": "Create Shots", "isVisible": scope_sequence, - "execute": main_window + "execute": FlameToFtrackPanel } ] } From 13edf52f93e6ce0e6d4bc437fe28a1365410a42c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Nov 2021 16:38:16 +0100 Subject: [PATCH 40/58] debugging modul names --- .../modules/{utils.py => app_utils.py} | 0 .../modules/{app.py => panel_app.py} | 70 +++++++++---------- .../openpype_flame_to_ftrack.py | 4 +- 3 files changed, 37 insertions(+), 37 deletions(-) rename openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/{utils.py => app_utils.py} (100%) rename openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/{app.py => panel_app.py} (90%) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/utils.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app_utils.py similarity index 100% rename from openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/utils.py rename to openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app_utils.py diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py similarity index 90% rename from openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py rename to openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index 6a654792f5..f49578db2f 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -3,12 +3,12 @@ from PySide2 import QtWidgets, QtCore import uiwidgets import flame -from .ftrack_lib import ( +from ftrack_lib import ( maintained_ftrack_session, FtrackEntityOperator, FtrackComponentCreator ) -from .utils import ( +from app_utils import ( get_config, set_config, get_all_task_types, @@ -19,7 +19,7 @@ from .utils import ( ) -class FlameToFtrackPanel(QtWidgets.QWidget()): +class FlameToFtrackPanel(object): temp_data_dir = None project_entity = None task_types = {} @@ -50,17 +50,16 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): }, } - def __init__(self, selection, *args, **kwargs): - super(FlameToFtrackPanel, self).__init__(*args, **kwargs) + def __init__(self, selection): self.selection = selection + self.window = QtWidgets.QWidget() # creating ui - self.setMinimumSize(1500, 600) - self.setWindowTitle('Sequence Shots to Ftrack') - self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.setStyleSheet('background-color: #313131') - + self.window.setMinimumSize(1500, 600) + self.window.setWindowTitle('Sequence Shots to Ftrack') + self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) + self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.window.setStyleSheet('background-color: #313131') self._create_tree_widget() self._set_sequence_params() @@ -81,54 +80,54 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): # input fields self.shot_name_label = uiwidgets.FlameLabel( - 'Shot name template', 'normal', self) + 'Shot name template', 'normal', self.window) self.shot_name_template_input = uiwidgets.FlameLineEdit( - cfg_d["shot_name_template"], self) + cfg_d["shot_name_template"], self.window) self.hierarchy_label = uiwidgets.FlameLabel( - 'Parents template', 'normal', self) + 'Parents template', 'normal', self.window) self.hierarchy_template_input = uiwidgets.FlameLineEdit( - cfg_d["hierarchy_template"], self) + cfg_d["hierarchy_template"], self.window) self.start_frame_label = uiwidgets.FlameLabel( - 'Workfile start frame', 'normal', self) + 'Workfile start frame', 'normal', self.window) self.start_frame_input = uiwidgets.FlameLineEdit( - cfg_d["workfile_start_frame"], self) + cfg_d["workfile_start_frame"], self.window) self.handles_label = uiwidgets.FlameLabel( - 'Shot handles', 'normal', self) + 'Shot handles', 'normal', self.window) self.handles_input = uiwidgets.FlameLineEdit( - cfg_d["shot_handles"], self) + cfg_d["shot_handles"], self.window) self.width_label = uiwidgets.FlameLabel( - 'Sequence width', 'normal', self) + 'Sequence width', 'normal', self.window) self.width_input = uiwidgets.FlameLineEdit( - str(self.seq_width), self) + str(self.seq_width), self.window) self.height_label = uiwidgets.FlameLabel( - 'Sequence height', 'normal', self) + 'Sequence height', 'normal', self.window) self.height_input = uiwidgets.FlameLineEdit( - str(self.seq_height), self) + str(self.seq_height), self.window) self.pixel_aspect_label = uiwidgets.FlameLabel( - 'Pixel aspect ratio', 'normal', self) + 'Pixel aspect ratio', 'normal', self.window) self.pixel_aspect_input = uiwidgets.FlameLineEdit( - str(1.00), self) + str(1.00), self.window) self.fps_label = uiwidgets.FlameLabel( - 'Frame rate', 'normal', self) + 'Frame rate', 'normal', self.window) self.fps_input = uiwidgets.FlameLineEdit( - str(self.fps), self) + str(self.fps), self.window) # Button self.select_all_btn = uiwidgets.FlameButton( - 'Select All', self.select_all, self) + 'Select All', self.select_all, self.window) self.remove_temp_data_btn = uiwidgets.FlameButton( - 'Remove temp data', self.remove_temp_data, self) + 'Remove temp data', self.remove_temp_data, self.window) self.ftrack_send_btn = uiwidgets.FlameButton( - 'Send to Ftrack', self._send_to_ftrack, self) + 'Send to Ftrack', self._send_to_ftrack, self.window) def _generate_layouts(self): # left props @@ -199,9 +198,9 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): self.task_types = get_all_task_types(self.project_entity) self.task_type_label = uiwidgets.FlameLabel( - 'Create Task (type)', 'normal', self) + 'Create Task (type)', 'normal', self.window) self.task_type_input = uiwidgets.FlamePushButtonMenu( - cfg_d["create_task_type"], self.task_types.keys(), self) + cfg_d["create_task_type"], self.task_types.keys(), self.window) def _create_project_widget(self): @@ -226,9 +225,9 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): self.all_task_types = {p["full_name"]: get_all_task_types( p).keys() for p in self.all_projects} self.project_select_label = uiwidgets.FlameLabel( - 'Select Ftrack project', 'normal', self) + 'Select Ftrack project', 'normal', self.window) self.project_select_input = uiwidgets.FlamePushButtonMenu( - self.project_entity["full_name"], project_names, self) + self.project_entity["full_name"], project_names, self.window) self.project_select_input.selection_changed.connect( self._on_project_changed) @@ -238,7 +237,8 @@ class FlameToFtrackPanel(QtWidgets.QWidget()): ordered_column_labels.pop(_value["order"]) ordered_column_labels.insert(_value["order"], _name) - self.tree = uiwidgets.FlameTreeWidget(ordered_column_labels, self) + self.tree = uiwidgets.FlameTreeWidget( + ordered_column_labels, self.window) # Allow multiple items in tree to be selected self.tree.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 96828ae971..a6383ea7fe 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -4,13 +4,13 @@ import os import sys try: - from app import FlameToFtrackPanel + from panel_app import FlameToFtrackPanel except ImportError: SCRIPT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") sys.path.append(PACKAGE_DIR) - from app import FlameToFtrackPanel + from panel_app import FlameToFtrackPanel def scope_sequence(selection): import flame From 05ef1311402d2c344c9f77ec17ac4375aa5f4ab4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Nov 2021 17:38:41 +0100 Subject: [PATCH 41/58] debugging panel functions --- .../modules/panel_app.py | 104 +++++++++--------- .../openpype_flame_to_ftrack.py | 8 +- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index f49578db2f..50eedbee77 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -3,20 +3,16 @@ from PySide2 import QtWidgets, QtCore import uiwidgets import flame +import ftrack_lib +reload(ftrack_lib) +import app_utils +reload(app_utils) + from ftrack_lib import ( maintained_ftrack_session, FtrackEntityOperator, FtrackComponentCreator ) -from app_utils import ( - get_config, - set_config, - get_all_task_types, - make_temp_dir, - export_thumbnail, - export_video, - timecode_to_frames -) class FlameToFtrackPanel(object): @@ -51,7 +47,7 @@ class FlameToFtrackPanel(object): } def __init__(self, selection): - + print(selection) self.selection = selection self.window = QtWidgets.QWidget() # creating ui @@ -61,21 +57,23 @@ class FlameToFtrackPanel(object): self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.window.setStyleSheet('background-color: #313131') - self._create_tree_widget() - self._set_sequence_params() + with maintained_ftrack_session() as session: + self._create_project_widget(session) + self._create_tree_widget() + self._set_sequence_params() + self._generate_widgets() + print(5) + self._generate_layouts() + print(6) + self._timeline_info() + print(7) + self._fix_resolution() + print(8) - self._create_project_widget() - - self._generate_widgets() - - self._generate_layouts() - - self._timeline_info() - - self._fix_resolution() + self.window.show() def _generate_widgets(self): - with get_config("main") as cfg_d: + with app_utils.get_config("main") as cfg_d: self._create_task_type_widget(cfg_d) # input fields @@ -153,7 +151,7 @@ class FlameToFtrackPanel(object): self.task_type_input, (v_shift + 4), 1) # right props - prop_widget_r = QtWidgets.QWidget(self) + prop_widget_r = QtWidgets.QWidget(self.window) prop_layout_r = QtWidgets.QGridLayout(prop_widget_r) prop_layout_r.setHorizontalSpacing(30) prop_layout_r.setAlignment( @@ -181,7 +179,7 @@ class FlameToFtrackPanel(object): hbox.addWidget(self.ftrack_send_btn) # put all layouts together - main_frame = QtWidgets.QVBoxLayout(self) + main_frame = QtWidgets.QVBoxLayout(self.window) main_frame.setMargin(20) main_frame.addLayout(prop_main_layout) main_frame.addWidget(self.tree) @@ -195,41 +193,41 @@ class FlameToFtrackPanel(object): break def _create_task_type_widget(self, cfg_d): - self.task_types = get_all_task_types(self.project_entity) + print(self.project_entity) + self.task_types = app_utils.get_all_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): + def _create_project_widget(self, session): - with maintained_ftrack_session() as session: - # get project name from flame current project - self.project_name = flame.project.current_project.name + # 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) + # 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 = session.query(query).first() + # globally used variables + self.project_entity = session.query(query).first() - self.project_selector_enabled = bool(not self.project_entity) + self.project_selector_enabled = bool(not self.project_entity) - if self.project_selector_enabled: - self.all_projects = 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"]: get_all_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) + if self.project_selector_enabled: + self.all_projects = 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"]: app_utils.get_all_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() @@ -272,7 +270,7 @@ class FlameToFtrackPanel(object): } # add cfg data back to settings.ini - set_config(_cfg_data_back, "main") + app_utils.set_config(_cfg_data_back, "main") def _send_to_ftrack(self): # resolve active project and add it to self.project_entity @@ -456,7 +454,7 @@ class FlameToFtrackPanel(object): continue # get clip frame duration record_duration = str(segment.record_duration)[1:-1] - clip_duration = timecode_to_frames( + clip_duration = app_utils.timecode_to_frames( record_duration, frame_rate) # populate shot source metadata @@ -500,9 +498,9 @@ class FlameToFtrackPanel(object): if self.temp_data_dir: return True - with make_temp_dir() as tempdir_path: + with app_utils.make_temp_dir() as tempdir_path: for seq in self.selection: - export_thumbnail(seq, tempdir_path, change_preset_data) - export_video(seq, tempdir_path, change_preset_data) + app_utils.export_thumbnail(seq, tempdir_path, change_preset_data) + app_utils.export_video(seq, tempdir_path, change_preset_data) self.temp_data_dir = tempdir_path break diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index a6383ea7fe..3c1063c445 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -4,13 +4,15 @@ import os import sys try: - from panel_app import FlameToFtrackPanel + import panel_app + reload(panel_app) except ImportError: SCRIPT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") sys.path.append(PACKAGE_DIR) - from panel_app import FlameToFtrackPanel + import panel_app + def scope_sequence(selection): import flame @@ -25,7 +27,7 @@ def get_media_panel_custom_ui_actions(): { "name": "Create Shots", "isVisible": scope_sequence, - "execute": FlameToFtrackPanel + "execute": panel_app.FlameToFtrackPanel } ] } From e4970603750067c5ad0ce12cc82f01e7668f6eb2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Nov 2021 17:44:44 +0100 Subject: [PATCH 42/58] debugging window move and json import --- .../openpype_flame_to_ftrack/modules/ftrack_lib.py | 11 ++++++----- .../openpype_flame_to_ftrack/modules/panel_app.py | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 8ea1cfc775..215e3d69ec 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -2,6 +2,7 @@ import os import sys import six import re +import json from contextlib import contextmanager # Fill following constants or set them via environment variable @@ -127,13 +128,13 @@ class FtrackComponentCreator: duration = data["duration"] handles = data["handles"] fps = data["fps"] - component_data.update({ - "metadata": {'ftr_meta': json.dumps({ + component_data["metadata"] = { + 'ftr_meta': json.dumps({ 'frameIn': int(0), 'frameOut': int(duration + (handles * 2)), - 'frameRate': float(fps)})} - }) - + 'frameRate': float(fps) + }) + } if not assetversion_entity: # get assettype entity from session assettype_entity = self._get_assettype({"short": "reference"}) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index 50eedbee77..4ef22c513d 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -434,9 +434,9 @@ class FlameToFtrackPanel(object): def _fix_resolution(self): # Center window in linux resolution = QtWidgets.QDesktopWidget().screenGeometry() - self.move( - (resolution.width() / 2) - (self.frameSize().width() / 2), - (resolution.height() / 2) - (self.frameSize().height() / 2)) + 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] From 1127d4cc003e35e150bf7dfc412e9c749108c75c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 20 Nov 2021 08:46:53 +0100 Subject: [PATCH 43/58] adding widget with closing event action --- .../openpype_flame_to_ftrack/modules/panel_app.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index 4ef22c513d..b9ae72d7f1 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -14,6 +14,17 @@ from ftrack_lib import ( FtrackComponentCreator ) +class MainWindow(QtWidgets.QWidget): + can_close = True + def __init__(self, *args, **kwargs): + super(MainWindow, self).__init__(*args, **kwargs) + def closeEvent(self, event): + print("______>>>____ closing app") + print(event) + if self.can_close: + event.accept() + else: + event.ignore() class FlameToFtrackPanel(object): temp_data_dir = None @@ -49,7 +60,7 @@ class FlameToFtrackPanel(object): def __init__(self, selection): print(selection) self.selection = selection - self.window = QtWidgets.QWidget() + self.window = MainWindow() # creating ui self.window.setMinimumSize(1500, 600) self.window.setWindowTitle('Sequence Shots to Ftrack') From d7f5510f331bf7db8f17b074cd76d5c5e77750b7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 20 Nov 2021 09:51:29 +0100 Subject: [PATCH 44/58] triggering clearing data --- .../modules/panel_app.py | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index b9ae72d7f1..09fa312e88 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -16,22 +16,24 @@ from ftrack_lib import ( class MainWindow(QtWidgets.QWidget): can_close = True - def __init__(self, *args, **kwargs): + def __init__(self, klass, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) + self.panel_class = klass + def closeEvent(self, event): - print("______>>>____ closing app") - print(event) - if self.can_close: - event.accept() - else: - event.ignore() + # clear all temp data + print("Removing temp data") + self.panel_class.clear_temp_data() + + # now the panel can be closed + event.accept() class FlameToFtrackPanel(object): temp_data_dir = None + processed_components = [] project_entity = None task_types = {} all_task_types = {} - processed_components = [] # TreeWidget columns = { @@ -60,7 +62,7 @@ class FlameToFtrackPanel(object): def __init__(self, selection): print(selection) self.selection = selection - self.window = MainWindow() + self.window = MainWindow(self) # creating ui self.window.setMinimumSize(1500, 600) self.window.setWindowTitle('Sequence Shots to Ftrack') @@ -133,7 +135,7 @@ class FlameToFtrackPanel(object): 'Select All', self.select_all, self.window) self.remove_temp_data_btn = uiwidgets.FlameButton( - 'Remove temp data', self.remove_temp_data, self.window) + 'Remove temp data', self.clear_temp_data, self.window) self.ftrack_send_btn = uiwidgets.FlameButton( 'Send to Ftrack', self._send_to_ftrack, self.window) @@ -498,12 +500,14 @@ class FlameToFtrackPanel(object): def select_all(self, ): self.tree.selectAll() - def remove_temp_data(self, ): + def clear_temp_data(self): import shutil if self.temp_data_dir: shutil.rmtree(self.temp_data_dir) self.temp_data_dir = None + self.processed_components = [] + print("All Temp data were destroied ...") def generate_temp_data(self, change_preset_data): if self.temp_data_dir: From b7a5eef4bff125a229f3fc67c8bd5a967762578c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 20 Nov 2021 10:09:55 +0100 Subject: [PATCH 45/58] processed components testing --- .../openpype_flame_to_ftrack/modules/panel_app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index 09fa312e88..82e9d3ba02 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -61,6 +61,9 @@ class FlameToFtrackPanel(object): def __init__(self, selection): print(selection) + print(self.processed_components) + self.processed_components = [] + print(self.processed_components) self.selection = selection self.window = MainWindow(self) # creating ui @@ -501,12 +504,13 @@ class FlameToFtrackPanel(object): self.tree.selectAll() def clear_temp_data(self): + self.processed_components = [] + import shutil if self.temp_data_dir: shutil.rmtree(self.temp_data_dir) self.temp_data_dir = None - self.processed_components = [] print("All Temp data were destroied ...") def generate_temp_data(self, change_preset_data): From 62a626969431a2298abcef475a040586bb35a140 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 22 Nov 2021 11:59:56 +0100 Subject: [PATCH 46/58] moving temp data generator to ftrack component creator --- .../modules/ftrack_lib.py | 36 +++++++++++++ .../modules/panel_app.py | 50 +++++++------------ 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 215e3d69ec..6ea35f6c22 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -5,6 +5,9 @@ import re import json from contextlib import contextmanager +import app_utils +reload(app_utils) + # Fill following constants or set them via environment variable FTRACK_MODULE_PATH = None FTRACK_API_KEY = None @@ -96,11 +99,44 @@ def maintained_ftrack_session(): 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, temp_folder, change_preset_data): + print(">>>>> self.temp_dir: " + self.temp_dir) + print(">>>>> self.thumbnails: " + str(self.thumbnails)) + print(">>>>> self.videos: " + str(self.videos)) + + if temp_folder == self.temp_dir: + return temp_folder + + 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) + temp_files = os.listdir(temp_folder) + 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 + return tempdir_path + + def get_thumb_path(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(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 diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index 82e9d3ba02..c5a4ffea97 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -74,9 +74,13 @@ class FlameToFtrackPanel(object): self.window.setStyleSheet('background-color: #313131') with maintained_ftrack_session() as session: + print(1) self._create_project_widget(session) + print(2) self._create_tree_widget() + print(3) self._set_sequence_params() + print(4) self._generate_widgets() print(5) self._generate_layouts() @@ -312,17 +316,13 @@ class FlameToFtrackPanel(object): session, self.project_entity) component_creator = FtrackComponentCreator(session) - self.generate_temp_data({ - "nbHandles": handles - }) - - temp_files = os.listdir(self.temp_data_dir) - thumbnails = [f for f in temp_files if "jpg" in f] - videos = [f for f in temp_files if "mov" in f] - - print(temp_files) - print(thumbnails) - print(videos) + self.temp_data_dir = component_creator.generate_temp_data( + self.selection, + self.temp_data_dir, + { + "nbHandles": handles + } + ) # Get all selected items from treewidget for item in self.tree.selectedItems(): @@ -338,18 +338,13 @@ class FlameToFtrackPanel(object): sequence_name = item.text(0) shot_name = item.text(1) - # get component files - thumb_f = next((f for f in thumbnails if shot_name in f), None) - video_f = next((f for f in videos if shot_name in f), None) - thumb_fp = os.path.join(self.temp_data_dir, thumb_f) - video_fp = os.path.join(self.temp_data_dir, video_f) - print(thumb_fp) - print(video_fp) + 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)) processed = False - if thumb_f not in self.processed_components: - self.processed_components.append(thumb_f) + if thumb_fp not in self.processed_components: + self.processed_components.append(thumb_fp) else: processed = True @@ -504,22 +499,11 @@ class FlameToFtrackPanel(object): self.tree.selectAll() def clear_temp_data(self): - self.processed_components = [] - 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 destroied ...") - - def generate_temp_data(self, change_preset_data): - if self.temp_data_dir: - return True - - with app_utils.make_temp_dir() as tempdir_path: - for seq in self.selection: - app_utils.export_thumbnail(seq, tempdir_path, change_preset_data) - app_utils.export_video(seq, tempdir_path, change_preset_data) - self.temp_data_dir = tempdir_path - break From 0bb87ca691dea7b9b09c996cb6c5d663539cf31e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 22 Nov 2021 17:12:20 +0100 Subject: [PATCH 47/58] improve work with ftrack session --- .../modules/app_utils.py | 12 - .../modules/ftrack_lib.py | 91 ++--- .../modules/panel_app.py | 356 +++++++++--------- 3 files changed, 220 insertions(+), 239 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app_utils.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app_utils.py index 2aa6577325..b255d8d3f5 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app_utils.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/app_utils.py @@ -99,18 +99,6 @@ create_task_type = Compositing """ -def get_all_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 - - def configure_preset(file_path, data): split_fp = os.path.splitext(file_path) new_file_path = split_fp[0] + "_tmp" + split_fp[-1] diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 6ea35f6c22..b074a0acf1 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -27,73 +27,56 @@ def import_ftrack_api(): return ftrack_api -@contextmanager -def maintained_ftrack_session(): +def get_ftrack_session(): import os ftrack_api = import_ftrack_api() - def validate_credentials(url, user, api): - 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: - session = ftrack_api.Session( - server_url=url, - api_user=user, - api_key=api - ) - session.close() - 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 - print( - 'Credentials Username: "{}", API key: "{}" are valid.'.format( - user, api) - ) - return True - # 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: - assert validate_credentials(url, user, api), ( - "Ftrack credentials failed") - # open ftrack session - session = ftrack_api.Session( + return ftrack_api.Session( server_url=url, api_user=user, api_key=api ) - yield session - except Exception: - tp, value, tb = sys.exc_info() - six.reraise(tp, value, tb) - finally: - # close the session - session.close() + 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: diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index c5a4ffea97..0e3a4ae4cf 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -9,7 +9,7 @@ import app_utils reload(app_utils) from ftrack_lib import ( - maintained_ftrack_session, + get_ftrack_session, FtrackEntityOperator, FtrackComponentCreator ) @@ -24,11 +24,13 @@ class MainWindow(QtWidgets.QWidget): # clear all temp data print("Removing temp data") self.panel_class.clear_temp_data() + self.panel_class.close() # now the panel can be closed event.accept() class FlameToFtrackPanel(object): + session = None temp_data_dir = None processed_components = [] project_entity = None @@ -62,8 +64,12 @@ class FlameToFtrackPanel(object): def __init__(self, selection): print(selection) print(self.processed_components) + + self.session = get_ftrack_session() + self.processed_components = [] print(self.processed_components) + self.selection = selection self.window = MainWindow(self) # creating ui @@ -73,79 +79,80 @@ class FlameToFtrackPanel(object): self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.window.setStyleSheet('background-color: #313131') - with maintained_ftrack_session() as session: - print(1) - self._create_project_widget(session) - print(2) - self._create_tree_widget() - print(3) - self._set_sequence_params() - print(4) - self._generate_widgets() - print(5) - self._generate_layouts() - print(6) - self._timeline_info() - print(7) - self._fix_resolution() - print(8) + print(1) + self._create_project_widget() + print(2) + self._create_tree_widget() + print(3) + self._set_sequence_params() + print(4) + self._generate_widgets() + print(5) + self._generate_layouts() + print(6) + self._timeline_info() + print(7) + self._fix_resolution() + print(8) self.window.show() def _generate_widgets(self): - with app_utils.get_config("main") as cfg_d: - self._create_task_type_widget(cfg_d) + with app_utils.get_config("main") as cfg_data: + cfg_d = cfg_data - # 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._create_task_type_widget(cfg_d) - self.hierarchy_label = uiwidgets.FlameLabel( - 'Parents template', 'normal', self.window) - self.hierarchy_template_input = uiwidgets.FlameLineEdit( - cfg_d["hierarchy_template"], self.window) + # 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.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.hierarchy_label = uiwidgets.FlameLabel( + 'Parents template', 'normal', self.window) + self.hierarchy_template_input = uiwidgets.FlameLineEdit( + cfg_d["hierarchy_template"], self.window) - self.handles_label = uiwidgets.FlameLabel( - 'Shot handles', 'normal', self.window) - self.handles_input = uiwidgets.FlameLineEdit( - cfg_d["shot_handles"], 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.width_label = uiwidgets.FlameLabel( - 'Sequence width', 'normal', self.window) - self.width_input = uiwidgets.FlameLineEdit( - str(self.seq_width), self.window) + self.handles_label = uiwidgets.FlameLabel( + 'Shot handles', 'normal', self.window) + self.handles_input = uiwidgets.FlameLineEdit( + cfg_d["shot_handles"], self.window) - self.height_label = uiwidgets.FlameLabel( - 'Sequence height', 'normal', self.window) - self.height_input = uiwidgets.FlameLineEdit( - str(self.seq_height), self.window) + self.width_label = uiwidgets.FlameLabel( + 'Sequence width', 'normal', self.window) + self.width_input = uiwidgets.FlameLineEdit( + str(self.seq_width), 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.height_label = uiwidgets.FlameLabel( + 'Sequence height', 'normal', self.window) + self.height_input = uiwidgets.FlameLineEdit( + str(self.seq_height), self.window) - self.fps_label = uiwidgets.FlameLabel( - 'Frame rate', 'normal', self.window) - self.fps_input = uiwidgets.FlameLineEdit( - str(self.fps), 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) - # Button - self.select_all_btn = uiwidgets.FlameButton( - 'Select All', self.select_all, self.window) + self.fps_label = uiwidgets.FlameLabel( + 'Frame rate', 'normal', self.window) + self.fps_input = uiwidgets.FlameLineEdit( + str(self.fps), self.window) - self.remove_temp_data_btn = uiwidgets.FlameButton( - 'Remove temp data', self.clear_temp_data, self.window) + # Button + self.select_all_btn = uiwidgets.FlameButton( + 'Select All', self.select_all, self.window) - self.ftrack_send_btn = uiwidgets.FlameButton( - 'Send to Ftrack', self._send_to_ftrack, 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 @@ -214,14 +221,15 @@ class FlameToFtrackPanel(object): def _create_task_type_widget(self, cfg_d): print(self.project_entity) - self.task_types = app_utils.get_all_task_types(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, session): + def _create_project_widget(self): # get project name from flame current project self.project_name = flame.project.current_project.name @@ -231,17 +239,19 @@ class FlameToFtrackPanel(object): query = 'Project where full_name is "{}"'.format(self.project_name) # globally used variables - self.project_entity = session.query(query).first() + 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 = session.query( + 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"]: app_utils.get_all_task_types( - p).keys() 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( @@ -309,138 +319,135 @@ class FlameToFtrackPanel(object): # get resolution from gui inputs fps = self.fps_input.text() - with maintained_ftrack_session() as session: - print("Ftrack session is: {}".format(session)) + entity_operator = FtrackEntityOperator( + self.session, self.project_entity) + component_creator = FtrackComponentCreator(self.session) - entity_operator = FtrackEntityOperator( - session, self.project_entity) - component_creator = FtrackComponentCreator(session) + self.temp_data_dir = component_creator.generate_temp_data( + self.selection, + self.temp_data_dir, + { + "nbHandles": handles + } + ) - self.temp_data_dir = component_creator.generate_temp_data( - self.selection, - self.temp_data_dir, - { - "nbHandles": handles - } - ) + # 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 - # 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) - # description - shot_description = item.text(3) - task_description = item.text(4) + # other + sequence_name = item.text(0) + shot_name = item.text(1) - # 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) - 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)) + processed = False + if thumb_fp not in self.processed_components: + self.processed_components.append(thumb_fp) + else: + processed = True - print("processed comps: {}".format(self.processed_components)) - processed = False - if thumb_fp not in self.processed_components: - self.processed_components.append(thumb_fp) - else: - processed = True + print("processed: {}".format(processed)) - print("processed: {}".format(processed)) + # populate full shot info + shot_attributes = { + "sequence": sequence_name, + "shot": shot_name, + "task": task_type + } - # 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 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) - # 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) - # 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( - session, - _type, - _name, - _parent - ) - print(p_entity) - _parent = p_entity - - # obtain shot ftrack entity - f_s_entity = entity_operator.get_ftrack_entity( - session, - "Shot", - _shot_name, + # obtain shot parents entities + _parent = None + for _name, _type in parents: + p_entity = entity_operator.get_ftrack_entity( + self.session, + _type, + _name, _parent ) - print("Shot entity is: {}".format(f_s_entity)) + print(p_entity) + _parent = p_entity - if not processed: - # first create thumbnail and get version entity - assetversion_entity = component_creator.create_comonent( - f_s_entity, { - "file_path": thumb_fp - } - ) + # 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)) - # 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 - ) + if not processed: + # first create thumbnail and get version entity + assetversion_entity = component_creator.create_comonent( + f_s_entity, { + "file_path": thumb_fp + } + ) - # 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) - } + # 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 + ) - # update custom attributes on shot entity - for key in custom_attrs: - f_s_entity['custom_attributes'][key] = custom_attrs[key] + # 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) + } - task_entity = entity_operator.create_task( - task_type, self.task_types, f_s_entity) + # update custom attributes on shot entity + for key in custom_attrs: + f_s_entity['custom_attributes'][key] = custom_attrs[key] - # Create notes. - user = session.query( - "User where username is \"{}\"".format(session.api_user) - ).first() + task_entity = entity_operator.create_task( + task_type, self.task_types, f_s_entity) - f_s_entity.create_note(shot_description, author=user) + # Create notes. + user = self.session.query( + "User where username is \"{}\"".format(self.session.api_user) + ).first() - if task_description: - task_entity.create_note(task_description, user) + f_s_entity.create_note(shot_description, author=user) - entity_operator.commit() + if task_description: + task_entity.create_note(task_description, user) - component_creator.close() + entity_operator.commit() + + component_creator.close() def _fix_resolution(self): # Center window in linux @@ -507,3 +514,6 @@ class FlameToFtrackPanel(object): shutil.rmtree(self.temp_data_dir) self.temp_data_dir = None print("All Temp data were destroied ...") + + def close(self): + self.session.close() From 36ce07c85e062e4a25857f4bd86d32ad00c29fbd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 22 Nov 2021 17:14:09 +0100 Subject: [PATCH 48/58] saving cfg on window close --- .../openpype_flame_to_ftrack/modules/panel_app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index 0e3a4ae4cf..0073771a83 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -516,4 +516,5 @@ class FlameToFtrackPanel(object): print("All Temp data were destroied ...") def close(self): + self._save_ui_state_to_cfg() self.session.close() From d179bae3214eda0786a4e4b5d94eee42905e205a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 22 Nov 2021 17:30:05 +0100 Subject: [PATCH 49/58] debugging --- .../openpype_flame_to_ftrack/modules/ftrack_lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index b074a0acf1..90ff0e267c 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -92,7 +92,7 @@ class FtrackComponentCreator: def generate_temp_data(self, selection, temp_folder, change_preset_data): - print(">>>>> self.temp_dir: " + self.temp_dir) + print(">>>>> self.temp_dir: " + str(self.temp_dir)) print(">>>>> self.thumbnails: " + str(self.thumbnails)) print(">>>>> self.videos: " + str(self.videos)) @@ -110,12 +110,12 @@ class FtrackComponentCreator: self.temp_dir = tempdir_path return tempdir_path - def get_thumb_path(shot_name): + 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(shot_name): + 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) From 59a70a29a82d0eae75102666044ac5bb081197f8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Nov 2021 12:11:50 +0100 Subject: [PATCH 50/58] temp path should be empty at first run --- .../openpype_flame_to_ftrack/modules/ftrack_lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 90ff0e267c..18f2261b56 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -96,7 +96,7 @@ class FtrackComponentCreator: print(">>>>> self.thumbnails: " + str(self.thumbnails)) print(">>>>> self.videos: " + str(self.videos)) - if temp_folder == self.temp_dir: + if self.temp_dir: return temp_folder with app_utils.make_temp_dir() as tempdir_path: From adba50492eac9241064a28a0a436d3fc485c2e32 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 26 Nov 2021 11:52:29 +0100 Subject: [PATCH 51/58] improving efectivity --- .../modules/ftrack_lib.py | 12 +----- .../modules/panel_app.py | 41 +++++++++---------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 18f2261b56..16ad41ebfa 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -90,21 +90,13 @@ class FtrackComponentCreator: self.session = session self._get_ftrack_location() - - def generate_temp_data(self, selection, temp_folder, change_preset_data): - print(">>>>> self.temp_dir: " + str(self.temp_dir)) - print(">>>>> self.thumbnails: " + str(self.thumbnails)) - print(">>>>> self.videos: " + str(self.videos)) - - if self.temp_dir: - return temp_folder - + 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) - temp_files = os.listdir(temp_folder) + 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 diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index 0073771a83..cedd5d6d02 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -1,3 +1,9 @@ +from ftrack_lib import ( + get_ftrack_session, + FtrackEntityOperator, + FtrackComponentCreator +) +import app_utils import os from PySide2 import QtWidgets, QtCore import uiwidgets @@ -5,17 +11,12 @@ import flame import ftrack_lib reload(ftrack_lib) -import app_utils reload(app_utils) -from ftrack_lib import ( - get_ftrack_session, - FtrackEntityOperator, - FtrackComponentCreator -) class MainWindow(QtWidgets.QWidget): can_close = True + def __init__(self, klass, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.panel_class = klass @@ -29,6 +30,7 @@ class MainWindow(QtWidgets.QWidget): # now the panel can be closed event.accept() + class FlameToFtrackPanel(object): session = None temp_data_dir = None @@ -77,23 +79,16 @@ class FlameToFtrackPanel(object): self.window.setWindowTitle('Sequence Shots to Ftrack') 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') - print(1) self._create_project_widget() - print(2) self._create_tree_widget() - print(3) self._set_sequence_params() - print(4) self._generate_widgets() - print(5) self._generate_layouts() - print(6) self._timeline_info() - print(7) self._fix_resolution() - print(8) self.window.show() @@ -323,13 +318,15 @@ class FlameToFtrackPanel(object): self.session, self.project_entity) component_creator = FtrackComponentCreator(self.session) - self.temp_data_dir = component_creator.generate_temp_data( - self.selection, - self.temp_data_dir, - { - "nbHandles": handles - } - ) + 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() # Get all selected items from treewidget for item in self.tree.selectedItems(): @@ -349,6 +346,8 @@ class FlameToFtrackPanel(object): 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) From ed5a8dc3f8843871826ce9886f96940e87ab3fcf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Nov 2021 12:56:29 +0100 Subject: [PATCH 52/58] adding collector for generated data --- .../openpype_flame_to_ftrack/modules/ftrack_lib.py | 14 +++++++++----- .../openpype_flame_to_ftrack/modules/panel_app.py | 3 +++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 16ad41ebfa..e3dad79a67 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -96,11 +96,15 @@ class FtrackComponentCreator: app_utils.export_thumbnail( seq, tempdir_path, change_preset_data) app_utils.export_video(seq, tempdir_path, change_preset_data) - 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 - return tempdir_path + + 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 diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index cedd5d6d02..692a0fe850 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -328,6 +328,9 @@ class FlameToFtrackPanel(object): ) 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 From 9ed9cf9ffe3e649d83e2be8806ffe78f69372635 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Nov 2021 13:20:48 +0100 Subject: [PATCH 53/58] fix problem with location --- .../modules/ftrack_lib.py | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index e3dad79a67..54c809660b 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -14,6 +14,7 @@ FTRACK_API_KEY = None FTRACK_API_USER = None FTRACK_SERVER = None +_ftrack_api = None def import_ftrack_api(): try: @@ -31,6 +32,7 @@ def get_ftrack_session(): import os ftrack_api = import_ftrack_api() + _ftrack_api = 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 "" @@ -179,6 +181,28 @@ class FtrackComponentCreator: origin_location = self._get_ftrack_location("ftrack.origin") location = data.pop("location") + + + entity["file_type"] = data["file_type"] + + origin_location.add_component( + entity, data["file_path"] + ) + + self._remove_component_from_location(entity, location) + + try: + # Add components to location. + location.add_component( + entity, origin_location, recursive=True) + except _ftrack_api.exception.ComponentInLocationError: + self._remove_component_from_location(entity, origin_location) + # Add components to location. + location.add_component( + entity, origin_location, recursive=True) + + + def _remove_component_from_location(self, location, entity): # Removing existing members from location components = list(entity.get("members", [])) components += [entity] @@ -200,16 +224,6 @@ class FtrackComponentCreator: if "members" in entity.keys(): entity["members"] = [] - entity["file_type"] = data["file_type"] - - origin_location.add_component( - entity, data["file_path"] - ) - - # Add components to location. - location.add_component( - entity, origin_location, recursive=True) - def _get_assettype(self, data): return self.session.query( self._query("AssetType", data)).first() From 23ecee1cf0daa598d63eaf1efabd7c6c5e0b3460 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Nov 2021 17:54:46 +0100 Subject: [PATCH 54/58] fixing remove component removing module ftrack api attribute --- .../modules/ftrack_lib.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 54c809660b..8321521d98 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -14,8 +14,6 @@ FTRACK_API_KEY = None FTRACK_API_USER = None FTRACK_SERVER = None -_ftrack_api = None - def import_ftrack_api(): try: import ftrack_api @@ -32,7 +30,6 @@ def get_ftrack_session(): import os ftrack_api = import_ftrack_api() - _ftrack_api = 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 "" @@ -181,34 +178,37 @@ class FtrackComponentCreator: 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"] - origin_location.add_component( - entity, data["file_path"] - ) - - self._remove_component_from_location(entity, location) - try: + origin_location.add_component( + entity, data["file_path"] + ) # Add components to location. location.add_component( entity, origin_location, recursive=True) - except _ftrack_api.exception.ComponentInLocationError: + 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, location, entity): + 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["component_locations"]: + for loc in component.get("component_locations", []): if location["id"] == loc["location_id"]: + print("<< Removing component: {}".format(component)) location.remove_component( component, recursive=False ) @@ -216,6 +216,7 @@ class FtrackComponentCreator: # 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() @@ -302,8 +303,8 @@ class FtrackComponentCreator: self.session.commit() except Exception: tp, value, tb = sys.exc_info() - self.session.rollback() - self.session._configure_locations() + # self.session.rollback() + # self.session._configure_locations() six.reraise(tp, value, tb) def _get_ftrack_location(self, name=None): From 13785ca7add51d647cd32a69b30585c9043e7bb5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 1 Dec 2021 11:23:36 +0100 Subject: [PATCH 55/58] finally solving issue with ftrack session closed needed to remove all modules and re-import them on start --- .../modules/ftrack_lib.py | 5 +-- .../modules/panel_app.py | 43 ++++++++++--------- .../openpype_flame_to_ftrack.py | 20 +++++---- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 8321521d98..1ceba18e57 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -6,7 +6,6 @@ import json from contextlib import contextmanager import app_utils -reload(app_utils) # Fill following constants or set them via environment variable FTRACK_MODULE_PATH = None @@ -14,6 +13,7 @@ FTRACK_API_KEY = None FTRACK_API_USER = None FTRACK_SERVER = None + def import_ftrack_api(): try: import ftrack_api @@ -104,7 +104,6 @@ class FtrackComponentCreator: 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) @@ -162,7 +161,7 @@ class FtrackComponentCreator: # get or create assetversion entity from session assetversion_entity = self._get_assetversion({ - "version": 1, + "version": 0, "asset": asset_entity }) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index 692a0fe850..d73a5c7013 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -1,21 +1,26 @@ -from ftrack_lib import ( - get_ftrack_session, - FtrackEntityOperator, - FtrackComponentCreator -) -import app_utils import os from PySide2 import QtWidgets, QtCore + import uiwidgets -import flame - +import app_utils import ftrack_lib -reload(ftrack_lib) -reload(app_utils) +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): - can_close = True def __init__(self, klass, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) @@ -26,7 +31,7 @@ class MainWindow(QtWidgets.QWidget): print("Removing temp data") self.panel_class.clear_temp_data() self.panel_class.close() - + clear_inner_modules() # now the panel can be closed event.accept() @@ -65,13 +70,8 @@ class FlameToFtrackPanel(object): def __init__(self, selection): print(selection) - print(self.processed_components) - - self.session = get_ftrack_session() - - self.processed_components = [] - print(self.processed_components) + self.session = ftrack_lib.get_ftrack_session() self.selection = selection self.window = MainWindow(self) # creating ui @@ -225,7 +225,7 @@ class FlameToFtrackPanel(object): 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 @@ -314,9 +314,9 @@ class FlameToFtrackPanel(object): # get resolution from gui inputs fps = self.fps_input.text() - entity_operator = FtrackEntityOperator( + entity_operator = ftrack_lib.FtrackEntityOperator( self.session, self.project_entity) - component_creator = FtrackComponentCreator(self.session) + component_creator = ftrack_lib.FtrackComponentCreator(self.session) if not self.temp_data_dir: self.window.hide() @@ -517,6 +517,7 @@ class FlameToFtrackPanel(object): self.temp_data_dir = None print("All Temp data were destroied ...") + def close(self): self._save_ui_state_to_cfg() self.session.close() diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 3c1063c445..688b8b6ae3 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -3,15 +3,19 @@ from __future__ import print_function import os import sys -try: - import panel_app - reload(panel_app) -except ImportError: - SCRIPT_DIR = os.path.dirname(__file__) - PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") - sys.path.append(PACKAGE_DIR) +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"] + print("panel_app module removed from sys.modules") import panel_app + panel_app.FlameToFtrackPanel(selection) def scope_sequence(selection): @@ -27,7 +31,7 @@ def get_media_panel_custom_ui_actions(): { "name": "Create Shots", "isVisible": scope_sequence, - "execute": panel_app.FlameToFtrackPanel + "execute": flame_panel_executor } ] } From 6048ebfcea3a6ca46441d12bf1349acb5ed53f97 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 1 Dec 2021 11:26:52 +0100 Subject: [PATCH 56/58] code clean --- .../openpype_flame_to_ftrack/modules/panel_app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py index d73a5c7013..9e39147776 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/panel_app.py @@ -1,10 +1,10 @@ -import os from PySide2 import QtWidgets, QtCore import uiwidgets import app_utils import ftrack_lib + def clear_inner_modules(): import sys @@ -20,6 +20,7 @@ def clear_inner_modules(): del sys.modules["uiwidgets"] print("uiwidgets module removed from sys.modules") + class MainWindow(QtWidgets.QWidget): def __init__(self, klass, *args, **kwargs): @@ -74,6 +75,7 @@ class FlameToFtrackPanel(object): self.session = ftrack_lib.get_ftrack_session() self.selection = selection self.window = MainWindow(self) + # creating ui self.window.setMinimumSize(1500, 600) self.window.setWindowTitle('Sequence Shots to Ftrack') @@ -517,7 +519,6 @@ class FlameToFtrackPanel(object): self.temp_data_dir = None print("All Temp data were destroied ...") - def close(self): self._save_ui_state_to_cfg() self.session.close() From 6552b576d83854540500fed49ae23e9d26f07110 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 7 Dec 2021 17:01:26 +0100 Subject: [PATCH 57/58] hound suggestions --- .../openpype_flame_to_ftrack/modules/ftrack_lib.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py index 1ceba18e57..26b197ee1d 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/ftrack_lib.py @@ -3,7 +3,6 @@ import sys import six import re import json -from contextlib import contextmanager import app_utils @@ -198,7 +197,6 @@ class FtrackComponentCreator: location.add_component( entity, origin_location, recursive=True) - def _remove_component_from_location(self, entity, location): print(location) # Removing existing members from location @@ -334,8 +332,8 @@ class FtrackComponentCreator: """ queries = [] if sys.version_info[0] < 3: - for key, value in data.iteritems(): - if not isinstance(value, (basestring, int)): + for key, value in data.items(): + if not isinstance(value, (str, int)): print("value: {}".format(value)) if "id" in value.keys(): queries.append( From 4dc9fb847089effdc2b2609c5d5cefdd10333bce Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 7 Dec 2021 17:10:44 +0100 Subject: [PATCH 58/58] hound suggestions --- .../modules/uiwidgets.py | 72 ++++++++++++------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/uiwidgets.py b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/uiwidgets.py index c04801da6f..0d4807a4ea 100644 --- a/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/uiwidgets.py +++ b/openpype/hosts/flame/utility_scripts/openpype_flame_to_ftrack/modules/uiwidgets.py @@ -5,7 +5,8 @@ class FlameLabel(QtWidgets.QLabel): """ Custom Qt Flame Label Widget - For different label looks set label_type as: 'normal', 'background', or 'outline' + For different label looks set label_type as: + 'normal', 'background', or 'outline' To use: @@ -24,23 +25,28 @@ class FlameLabel(QtWidgets.QLabel): # Set label stylesheet based on label_type if label_type == 'normal': - self.setStyleSheet('QLabel {color: #9a9a9a; border-bottom: 1px inset #282828; font: 14px "Discreet"}' - 'QLabel:disabled {color: #6a6a6a}') + 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"') + '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"') + '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) + Main window should include this: + window.setFocusPolicy(QtCore.Qt.StrongFocus) To use: @@ -54,9 +60,11 @@ class FlameLineEdit(QtWidgets.QLineEdit): 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"}' - 'QLineEdit:focus {background-color: #474e58}' - 'QLineEdit:disabled {color: #6a6a6a; background-color: #373737}') + 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): @@ -79,11 +87,11 @@ class FlameTreeWidget(QtWidgets.QTreeWidget): self.setAlternatingRowColors(True) self.setFocusPolicy(QtCore.Qt.NoFocus) self.setStyleSheet( - 'QTreeWidget {color: #9a9a9a; background-color: #2a2a2a; alternate-background-color: #2d2d2d; font: 14px "Discreet"}' - 'QTreeWidget::item:selected {color: #d9d9d9; background-color: #474747; border: 1px solid #111111}' - 'QHeaderView {color: #9a9a9a; background-color: #393939; font: 14px "Discreet"}' + '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"}' + 'QMenu {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' # noqa 'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}' ) self.verticalScrollBar().setStyleSheet('color: #818181') @@ -110,9 +118,11 @@ class FlameButton(QtWidgets.QPushButton): 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"}' - 'QPushButton:pressed {color: #d9d9d9; background-color: #4f4f4f; border-top: 1px inset #666666; font: italic}' - 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') + 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): @@ -135,10 +145,12 @@ class FlamePushButton(QtWidgets.QPushButton): 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"}' - '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}' - '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}' - 'QToolTip {color: black; background-color: #ffffde; border: black solid 1px}') + 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): @@ -148,12 +160,14 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): 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) + 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) + menu_push_button = FlamePushButtonMenu(push_button_menu_options[0], + push_button_menu_options, window) """ selection_changed = QtCore.Signal(str) @@ -165,13 +179,17 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): self.setMinimumHeight(28) self.setMinimumWidth(110) self.setFocusPolicy(QtCore.Qt.NoFocus) - self.setStyleSheet('QPushButton {color: #9a9a9a; background-color: #24303d; font: 14px "Discreet"}' - 'QPushButton:disabled {color: #747474; background-color: #353535; border-top: 1px solid #444444; border-bottom: 1px solid #242424}') + 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"}' - 'QMenu::item:selected {color: #d9d9d9; background-color: #3a4551}') + 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) @@ -191,4 +209,4 @@ class FlamePushButtonMenu(QtWidgets.QPushButton): def _on_action_trigger(self): action = self.sender() self.setText(action.text()) - self.selection_changed.emit(action.text()) \ No newline at end of file + self.selection_changed.emit(action.text())