From 28cf9e8daf90a73304c921bf793dce20f2c6d42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 3 May 2019 19:28:11 +0000 Subject: [PATCH 01/28] fix(action): fixed ftrack job killer - case where job has no user --- pype/ftrack/actions/action_job_killer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/actions/action_job_killer.py b/pype/ftrack/actions/action_job_killer.py index 00cb0f7719..5a1311f82f 100644 --- a/pype/ftrack/actions/action_job_killer.py +++ b/pype/ftrack/actions/action_job_killer.py @@ -46,7 +46,10 @@ class JobKiller(BaseAction): desctiption = data['description'] except Exception: desctiption = '*No description*' - user = job['user']['username'] + try: + user = job['user']['username'] + except Exception: + user = '*No user' created = job['created_at'].strftime('%d.%m.%Y %H:%M:%S') label = '{} - {} - {}'.format( desctiption, created, user From c8395f43dda60a79b51ab56643d6dee9b22c5e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Sun, 12 May 2019 22:41:40 +0000 Subject: [PATCH 02/28] fix(ftrack): check if asset is in avalon db before trying to delete it, throw sanitized error otherwise --- pype/ftrack/actions/action_delete_asset.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/ftrack/actions/action_delete_asset.py b/pype/ftrack/actions/action_delete_asset.py index eabadecee6..96087f4c8e 100644 --- a/pype/ftrack/actions/action_delete_asset.py +++ b/pype/ftrack/actions/action_delete_asset.py @@ -85,6 +85,12 @@ class DeleteAsset(BaseAction): 'type': 'asset', 'name': entity['name'] }) + + if av_entity is None: + return { + 'success': False, + 'message': 'Didn\'t found assets in avalon' + } asset_label = { 'type': 'label', From 1b33c88ca58156d276fb6bda1f628453540c500a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:04:32 +0200 Subject: [PATCH 03/28] component also returns start/end frame and frame rate (fps) if have these info in input data --- .../widgets/widget_component_item.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_component_item.py b/pype/standalonepublish/widgets/widget_component_item.py index 2e0df9a00c..14f6a8312d 100644 --- a/pype/standalonepublish/widgets/widget_component_item.py +++ b/pype/standalonepublish/widgets/widget_component_item.py @@ -10,11 +10,16 @@ class ComponentItem(QtWidgets.QFrame): C_HOVER = '#ffffff' C_ACTIVE = '#4BB543' C_ACTIVE_HOVER = '#4BF543' + signal_remove = QtCore.Signal(object) signal_thumbnail = QtCore.Signal(object) signal_preview = QtCore.Signal(object) signal_repre_change = QtCore.Signal(object, object) + startFrame = None + endFrame = None + frameRate = None + def __init__(self, parent, main_parent): super().__init__() self.has_valid_repre = True @@ -291,4 +296,12 @@ class ComponentItem(QtWidgets.QFrame): 'thumbnail': self.is_thumbnail(), 'preview': self.is_preview() } + + if ('startFrame' in self.in_data and 'endFrame' in self.in_data): + data['startFrame'] = self.in_data['startFrame'] + data['endFrame'] = self.in_data['endFrame'] + + if 'frameRate' in self.in_data: + data['frameRate'] = self.in_data['frameRate'] + return data From 9000ca0b83eb94c20ccc390e6a8b730b59df9833 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:05:41 +0200 Subject: [PATCH 04/28] removed get_ranges (death code) --- .../widgets/widget_drop_frame.py | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index cffe673152..de2bbe19a3 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -182,34 +182,8 @@ class DropDataFrame(QtWidgets.QFrame): 'is_sequence': True, 'actions': actions } - self._process_data(data) - def _get_ranges(self, indexes): - if len(indexes) == 1: - return str(indexes[0]) - ranges = [] - first = None - last = None - for index in indexes: - if first is None: - first = index - last = index - elif (last+1) == index: - last = index - else: - if first == last: - range = str(first) - else: - range = '{}-{}'.format(first, last) - ranges.append(range) - first = index - last = index - if first == last: - range = str(first) - else: - range = '{}-{}'.format(first, last) - ranges.append(range) - return ', '.join(ranges) + self._process_data(data) def _process_remainder(self, remainder): filename = os.path.basename(remainder) From 56d1b8d1e48f2d3d9c82ec6adede46cf196b7f06 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:06:14 +0200 Subject: [PATCH 05/28] pasted path from clipboard is normpathed --- pype/standalonepublish/widgets/widget_drop_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index de2bbe19a3..6be69584d0 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -49,7 +49,7 @@ class DropDataFrame(QtWidgets.QFrame): else: # If path is in clipboard as string try: - path = ent.text() + path = os.path.normpath(ent.text()) if os.path.exists(path): paths.append(path) else: From da376847404bc61d0fc9d5b37be60e5ca52fa5f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:06:40 +0200 Subject: [PATCH 06/28] collections add start and end frame into data --- pype/standalonepublish/widgets/widget_drop_frame.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index 6be69584d0..2fd14c26c7 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -170,6 +170,13 @@ class DropDataFrame(QtWidgets.QFrame): repr_name = file_ext.replace('.', '') range = collection.format('{ranges}') + # TODO: ranges must not be with missing frames!!! + # - this is goal implementation: + # startFrame, endFrame = range.split('-') + rngs = range.split(',') + startFrame = rngs[0].split('-')[0] + endFrame = rngs[-1].split('-')[-1] + actions = [] data = { @@ -177,6 +184,8 @@ class DropDataFrame(QtWidgets.QFrame): 'name': file_base, 'ext': file_ext, 'file_info': range, + 'startFrame': startFrame, + 'endFrame': endFrame, 'representation': repr_name, 'folder_path': folder_path, 'is_sequence': True, From 9c0c3a346559d1c2e9b495ca4b7c75959c441549 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:07:45 +0200 Subject: [PATCH 07/28] added method for enhanced getting data from ffprobe --- .../widgets/widget_drop_frame.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index 2fd14c26c7..1fe2777826 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -1,5 +1,6 @@ import os import re +import json import clique import subprocess from pypeapp import config @@ -244,6 +245,25 @@ class DropDataFrame(QtWidgets.QFrame): break except Exception as e: pass + def load_data_with_probe(self, filepath): + args = [ + 'ffprobe', + '-v', 'quiet', + '-print_format', 'json', + '-show_format', + '-show_streams', filepath + ] + ffprobe_p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + shell=True + ) + ffprobe_output = ffprobe_p.communicate()[0] + if ffprobe_p.returncode != 0: + raise RuntimeError( + 'Failed on ffprobe: check if ffprobe path is set in PATH env' + ) + return json.loads(ffprobe_output)['streams'][0] return output def _process_data(self, data): From ec38c4b048307a4b97afe29f7dcb2b4018aa6eb1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:08:44 +0200 Subject: [PATCH 08/28] get file_info replaced with get_file_data which collect more information --- .../widgets/widget_drop_frame.py | 69 ++++++++++++------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index 1fe2777826..4e99f697cb 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -216,35 +216,9 @@ class DropDataFrame(QtWidgets.QFrame): 'is_sequence': False, 'actions': actions } - data['file_info'] = self.get_file_info(data) self._process_data(data) - def get_file_info(self, data): - output = None - if data['ext'] == '.mov': - try: - # ffProbe must be in PATH - filepath = data['files'][0] - args = ['ffprobe', '-show_streams', filepath] - p = subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True - ) - datalines=[] - for line in iter(p.stdout.readline, b''): - line = line.decode("utf-8").replace('\r\n', '') - datalines.append(line) - - find_value = 'codec_name' - for line in datalines: - if line.startswith(find_value): - output = line.replace(find_value + '=', '') - break - except Exception as e: - pass def load_data_with_probe(self, filepath): args = [ 'ffprobe', @@ -264,10 +238,53 @@ class DropDataFrame(QtWidgets.QFrame): 'Failed on ffprobe: check if ffprobe path is set in PATH env' ) return json.loads(ffprobe_output)['streams'][0] + + def get_file_data(self, data): + filepath = data['files'][0] + ext = data['ext'] + output = {} + probe_data = self.load_data_with_probe(filepath) + + if ( + ext in self.presets['extensions']['image_file'] or + ext in self.presets['extensions']['video_file'] + ): + if 'frameRate' not in data: + # default value + frameRate = 25 + frameRate_string = probe_data.get('r_frame_rate') + if frameRate_string: + frameRate = int(frameRate_string.split('/')[0]) + + output['frameRate'] = frameRate + + if 'startFrame' not in data or 'endFrame' not in data: + startFrame = endFrame = 1 + endFrame_string = probe_data.get('nb_frames') + + if endFrame_string: + endFrame = int(endFrame_string) + + output['startFrame'] = startFrame + output['endFrame'] = endFrame + + file_info = None + if 'file_info' in data: + file_info = data['file_info'] + elif ext in ['.mov']: + file_info = probe_data.get('codec_name') + + output['file_info'] = file_info + return output def _process_data(self, data): ext = data['ext'] + # load file data info + file_data = self.get_file_data(data) + for key, value in file_data.items(): + data[key] = value + icon = 'default' for ico, exts in self.presets['extensions'].items(): if ext in exts: From 13d551ba72f5d3b663ff804ac9033f33d6454ceb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:10:25 +0200 Subject: [PATCH 09/28] removed not used code --- pype/standalonepublish/widgets/widget_component_item.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_component_item.py b/pype/standalonepublish/widgets/widget_component_item.py index 14f6a8312d..43aa54a955 100644 --- a/pype/standalonepublish/widgets/widget_component_item.py +++ b/pype/standalonepublish/widgets/widget_component_item.py @@ -16,10 +16,6 @@ class ComponentItem(QtWidgets.QFrame): signal_preview = QtCore.Signal(object) signal_repre_change = QtCore.Signal(object, object) - startFrame = None - endFrame = None - frameRate = None - def __init__(self, parent, main_parent): super().__init__() self.has_valid_repre = True From 08712b4cb5e228227e8e81a70ef0e3a021acd714 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 14:49:22 +0200 Subject: [PATCH 10/28] version spinbox added to middle widget so user can choose version --- pype/standalonepublish/widgets/__init__.py | 5 +- .../widgets/widget_family.py | 62 ++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/pype/standalonepublish/widgets/__init__.py b/pype/standalonepublish/widgets/__init__.py index cd99e15bed..4c6a0e85a5 100644 --- a/pype/standalonepublish/widgets/__init__.py +++ b/pype/standalonepublish/widgets/__init__.py @@ -20,15 +20,14 @@ from .model_tree_view_deselectable import DeselectableTreeView from .widget_asset_view import AssetView from .widget_asset import AssetWidget + from .widget_family_desc import FamilyDescriptionWidget from .widget_family import FamilyWidget from .widget_drop_empty import DropEmpty from .widget_component_item import ComponentItem from .widget_components_list import ComponentsList - from .widget_drop_frame import DropDataFrame - from .widget_components import ComponentsWidget -from.widget_shadow import ShadowWidget +from .widget_shadow import ShadowWidget diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 7259ecdb64..a2276bf7f9 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -51,6 +51,19 @@ class FamilyWidget(QtWidgets.QWidget): name_layout.addWidget(btn_subset) name_layout.setContentsMargins(0, 0, 0, 0) + # version + version_spinbox = QtWidgets.QSpinBox() + version_spinbox.setMinimum(1) + version_spinbox.setMaximum(9999) + version_spinbox.setEnabled(False) + + version_checkbox = QtWidgets.QCheckBox("Next Available Version") + version_checkbox.setCheckState(QtCore.Qt.CheckState(2)) + + version_layout = QtWidgets.QHBoxLayout() + version_layout.addWidget(version_spinbox) + version_layout.addWidget(version_checkbox) + layout = QtWidgets.QVBoxLayout(container) header = FamilyDescriptionWidget(self) @@ -63,6 +76,8 @@ class FamilyWidget(QtWidgets.QWidget): layout.addWidget(QtWidgets.QLabel("Subset")) layout.addLayout(name_layout) layout.addWidget(input_result) + layout.addWidget(QtWidgets.QLabel("Version")) + layout.addLayout(version_layout) layout.setContentsMargins(0, 0, 0, 0) options = QtWidgets.QWidget() @@ -86,6 +101,7 @@ class FamilyWidget(QtWidgets.QWidget): input_asset.textChanged.connect(self.on_data_changed) list_families.currentItemChanged.connect(self.on_selection_changed) list_families.currentItemChanged.connect(header.set_item) + version_checkbox.stateChanged.connect(self.on_version_refresh) self.stateChanged.connect(self._on_state_changed) @@ -95,6 +111,8 @@ class FamilyWidget(QtWidgets.QWidget): self.list_families = list_families self.input_asset = input_asset self.input_result = input_result + self.version_checkbox = version_checkbox + self.version_spinbox = version_spinbox self.refresh() @@ -103,7 +121,8 @@ class FamilyWidget(QtWidgets.QWidget): family = plugin.family.rsplit(".", 1)[-1] data = { 'family': family, - 'subset': self.input_subset.text() + 'subset': self.input_subset.text(), + 'version': self.version_spinbox.value() } return data @@ -204,6 +223,8 @@ class FamilyWidget(QtWidgets.QWidget): if asset_name != self.parent_widget.NOT_SELECTED: self.echo("'%s' not found .." % asset_name) + self.on_version_refresh() + # Update the valid state valid = ( subset_name.strip() != "" and @@ -213,6 +234,45 @@ class FamilyWidget(QtWidgets.QWidget): ) self.stateChanged.emit(valid) + def on_version_refresh(self): + auto_version = self.version_checkbox.isChecked() + self.version_spinbox.setEnabled(not auto_version) + if not auto_version: + return + + version = 1 + + asset_name = self.input_asset.text() + subset_name = self.input_result.text() + if ( + ( + asset_name.strip() != '' or + asset_name == self.parent_widget.NOT_SELECTED + ) and subset_name.strip() != '' + ): + asset = self.db.find_one({ + 'type': 'asset', + 'name': asset_name + }) + subset = self.db.find_one({ + 'type': 'subset', + 'parent': asset['_id'], + 'name': subset_name + }) + if subset: + versions = self.db.find({ + 'type': 'version', + 'parent': subset['_id'] + }) + if versions: + versions = sorted( + [v for v in versions], + key=lambda ver: ver['name'] + ) + version = int(versions[-1]['name']) + 1 + + self.version_spinbox.setValue(version) + def on_data_changed(self, *args): # Set invalid state until it's reconfirmed to be valid by the From 58d941d76df7326887d97043dab7605abc7a2c24 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 15:16:20 +0200 Subject: [PATCH 11/28] fixed asset not selected bug --- pype/standalonepublish/widgets/widget_family.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index a2276bf7f9..9a347cbeab 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -245,10 +245,8 @@ class FamilyWidget(QtWidgets.QWidget): asset_name = self.input_asset.text() subset_name = self.input_result.text() if ( - ( - asset_name.strip() != '' or - asset_name == self.parent_widget.NOT_SELECTED - ) and subset_name.strip() != '' + asset_name != self.parent_widget.NOT_SELECTED and + subset_name.strip() != '' ): asset = self.db.find_one({ 'type': 'asset', From f472285f657f7e83cb7736c0d28d849ea6e29de2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 15:16:51 +0200 Subject: [PATCH 12/28] changed font color of not enabled inputs so its readable --- pype/standalonepublish/widgets/widget_family.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 9a347cbeab..102705f98b 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -35,7 +35,7 @@ class FamilyWidget(QtWidgets.QWidget): input_asset.setStyleSheet("color: #BBBBBB;") input_subset = QtWidgets.QLineEdit() input_result = QtWidgets.QLineEdit() - input_result.setStyleSheet("color: gray;") + input_result.setStyleSheet("color: #BBBBBB;") input_result.setEnabled(False) # region Menu for default subset names @@ -56,6 +56,7 @@ class FamilyWidget(QtWidgets.QWidget): version_spinbox.setMinimum(1) version_spinbox.setMaximum(9999) version_spinbox.setEnabled(False) + version_spinbox.setStyleSheet("color: #BBBBBB;") version_checkbox = QtWidgets.QCheckBox("Next Available Version") version_checkbox.setCheckState(QtCore.Qt.CheckState(2)) From 4e74bda9fa40e9aff64d13aa87462dbbcc939dae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:20:43 +0200 Subject: [PATCH 13/28] NOT_SELECTED moved into family widget --- pype/standalonepublish/app.py | 3 +-- pype/standalonepublish/widgets/widget_family.py | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pype/standalonepublish/app.py b/pype/standalonepublish/app.py index 956cdb6300..5d3bfd0505 100644 --- a/pype/standalonepublish/app.py +++ b/pype/standalonepublish/app.py @@ -26,7 +26,6 @@ class Window(QtWidgets.QDialog): initialized = False WIDTH = 1100 HEIGHT = 500 - NOT_SELECTED = '< Nothing is selected >' def __init__(self, parent=None): super(Window, self).__init__(parent=parent) @@ -160,7 +159,7 @@ class Window(QtWidgets.QDialog): self.widget_family.change_asset(asset['name']) else: self.valid_parent = False - self.widget_family.change_asset(self.NOT_SELECTED) + self.widget_family.change_asset(None) self.widget_family.on_data_changed() def keyPressEvent(self, event): diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 102705f98b..117fb30151 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -17,12 +17,14 @@ class FamilyWidget(QtWidgets.QWidget): data = dict() _jobs = dict() Separator = "---separator---" + NOT_SELECTED = '< Nothing is selected >' def __init__(self, parent): super().__init__(parent) # Store internal states in here self.state = {"valid": False} self.parent_widget = parent + self.asset_name = self.NOT_SELECTED body = QtWidgets.QWidget() lists = QtWidgets.QWidget() @@ -132,7 +134,10 @@ class FamilyWidget(QtWidgets.QWidget): return self.parent_widget.db def change_asset(self, name): - self.input_asset.setText(name) + if name is None: + name = self.NOT_SELECTED + self.asset_name = name + self.on_data_changed() def _on_state_changed(self, state): self.state['valid'] = state From 941bf8e44c73d1dda18597a93e280f5805e0e2ed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:23:18 +0200 Subject: [PATCH 14/28] input asset removed from widgets and subset result is filled even if asset is not selected --- .../widgets/widget_family.py | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 117fb30151..1029f71593 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -32,9 +32,7 @@ class FamilyWidget(QtWidgets.QWidget): container = QtWidgets.QWidget() list_families = QtWidgets.QListWidget() - input_asset = QtWidgets.QLineEdit() - input_asset.setEnabled(False) - input_asset.setStyleSheet("color: #BBBBBB;") + input_subset = QtWidgets.QLineEdit() input_result = QtWidgets.QLineEdit() input_result.setStyleSheet("color: #BBBBBB;") @@ -74,8 +72,6 @@ class FamilyWidget(QtWidgets.QWidget): layout.addWidget(QtWidgets.QLabel("Family")) layout.addWidget(list_families) - layout.addWidget(QtWidgets.QLabel("Asset")) - layout.addWidget(input_asset) layout.addWidget(QtWidgets.QLabel("Subset")) layout.addLayout(name_layout) layout.addWidget(input_result) @@ -93,6 +89,7 @@ class FamilyWidget(QtWidgets.QWidget): layout.setContentsMargins(0, 0, 0, 0) layout = QtWidgets.QVBoxLayout(body) + layout.addWidget(lists) layout.addWidget(options, 0, QtCore.Qt.AlignLeft) layout.setContentsMargins(0, 0, 0, 0) @@ -101,7 +98,6 @@ class FamilyWidget(QtWidgets.QWidget): layout.addWidget(body) input_subset.textChanged.connect(self.on_data_changed) - input_asset.textChanged.connect(self.on_data_changed) list_families.currentItemChanged.connect(self.on_selection_changed) list_families.currentItemChanged.connect(header.set_item) version_checkbox.stateChanged.connect(self.on_version_refresh) @@ -112,7 +108,6 @@ class FamilyWidget(QtWidgets.QWidget): self.menu_subset = menu_subset self.btn_subset = btn_subset self.list_families = list_families - self.input_asset = input_asset self.input_result = input_result self.version_checkbox = version_checkbox self.version_spinbox = version_spinbox @@ -178,22 +173,37 @@ class FamilyWidget(QtWidgets.QWidget): self.input_subset.setText(action.text()) def _on_data_changed(self): - item = self.list_families.currentItem() + asset_name = self.asset_name subset_name = self.input_subset.text() - asset_name = self.input_asset.text() + item = self.list_families.currentItem() - # Get the assets from the database which match with the name - assets_db = self.db.find(filter={"type": "asset"}, projection={"name": 1}) - assets = [asset for asset in assets_db if asset_name in asset["name"]] if item is None: return - if assets: - # Get plugin and family - plugin = item.data(PluginRole) - if plugin is None: - return - family = plugin.family.rsplit(".", 1)[-1] + assets = None + if asset_name != self.NOT_SELECTED: + # Get the assets from the database which match with the name + assets_db = self.db.find( + filter={"type": "asset"}, + projection={"name": 1} + ) + assets = [ + asset for asset in assets_db if asset_name in asset["name"] + ] + + # Get plugin and family + plugin = item.data(PluginRole) + if plugin is None: + return + + family = plugin.family.rsplit(".", 1)[-1] + + # Update the result + if subset_name: + subset_name = subset_name[0].upper() + subset_name[1:] + self.input_result.setText("{}{}".format(family, subset_name)) + + if assets: # Get all subsets of the current asset asset_ids = [asset["_id"] for asset in assets] subsets = self.db.find(filter={"type": "subset", @@ -216,25 +226,20 @@ class FamilyWidget(QtWidgets.QWidget): self._build_menu(defaults) - # Update the result - if subset_name: - subset_name = subset_name[0].upper() + subset_name[1:] - self.input_result.setText("{}{}".format(family, subset_name)) - item.setData(ExistsRole, True) self.echo("Ready ..") else: self._build_menu([]) item.setData(ExistsRole, False) - if asset_name != self.parent_widget.NOT_SELECTED: + if asset_name != self.NOT_SELECTED: self.echo("'%s' not found .." % asset_name) self.on_version_refresh() # Update the valid state valid = ( + asset_name != self.NOT_SELECTED and subset_name.strip() != "" and - asset_name.strip() != "" and item.data(QtCore.Qt.ItemIsEnabled) and item.data(ExistsRole) ) @@ -246,12 +251,12 @@ class FamilyWidget(QtWidgets.QWidget): if not auto_version: return + asset_name = self.asset_name + subset_name = self.input_result.text() version = 1 - asset_name = self.input_asset.text() - subset_name = self.input_result.text() if ( - asset_name != self.parent_widget.NOT_SELECTED and + asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): asset = self.db.find_one({ From 2604e9178c8f3342be49abd0a58268ec22e2f86d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:23:50 +0200 Subject: [PATCH 15/28] changed icon for tasks nodes --- .../widgets/model_tasks_template.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pype/standalonepublish/widgets/model_tasks_template.py b/pype/standalonepublish/widgets/model_tasks_template.py index 4af3b9eea7..bd1984029c 100644 --- a/pype/standalonepublish/widgets/model_tasks_template.py +++ b/pype/standalonepublish/widgets/model_tasks_template.py @@ -8,13 +8,13 @@ class TasksTemplateModel(TreeModel): COLUMNS = ["Tasks"] - def __init__(self): + def __init__(self, selectable=True): super(TasksTemplateModel, self).__init__() - self.selectable = False - self._icons = { - "__default__": awesome.icon("fa.folder-o", - color=style.colors.default) - } + self.selectable = selectable + self.icon = awesome.icon( + 'fa.calendar-check-o', + color=style.colors.default + ) def set_tasks(self, tasks): """Set assets to track by their database id @@ -32,13 +32,11 @@ class TasksTemplateModel(TreeModel): self.beginResetModel() - icon = self._icons["__default__"] for task in tasks: node = Node({ "Tasks": task, - "icon": icon + "icon": self.icon }) - self.add_child(node) self.endResetModel() From 4123cdfb165d74fa242f9929dd8b8af6decc2ad4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:26:09 +0200 Subject: [PATCH 16/28] added tasks view into asset widget --- .../standalonepublish/widgets/widget_asset.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index 45e9757d71..e7d72a9db0 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -2,6 +2,8 @@ import contextlib from . import QtWidgets, QtCore from . import RecursiveSortFilterProxyModel, AssetModel, AssetView from . import awesome, style +from . import TasksTemplateModel, DeselectableTreeView + @contextlib.contextmanager def preserve_expanded_rows(tree_view, @@ -128,7 +130,7 @@ class AssetWidget(QtWidgets.QWidget): self.parent_widget = parent - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(4) @@ -163,12 +165,29 @@ class AssetWidget(QtWidgets.QWidget): layout.addLayout(header) layout.addWidget(view) + # tasks + task_view = DeselectableTreeView() + task_view.setIndentation(0) + task_view.setHeaderHidden(True) + + task_model = TasksTemplateModel() + task_view.setModel(task_model) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(4) + main_layout.addLayout(layout, 80) + main_layout.addWidget(task_view, 20) + # Signals/Slots selection = view.selectionModel() selection.selectionChanged.connect(self.selection_changed) selection.currentChanged.connect(self.current_changed) refresh.clicked.connect(self.refresh) + + self.task_view = task_view + self.task_model = task_model self.refreshButton = refresh self.model = model self.proxy = proxy From 9f5d932e0a2eff60246e61791738a1a6d03f9e8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:26:33 +0200 Subject: [PATCH 17/28] tasks widget now loads tasks from selected asset --- pype/standalonepublish/widgets/widget_asset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index e7d72a9db0..92a4d1d88f 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -185,6 +185,7 @@ class AssetWidget(QtWidgets.QWidget): selection.currentChanged.connect(self.current_changed) refresh.clicked.connect(self.refresh) + self.selection_changed.connect(self._refresh_tasks) self.task_view = task_view self.task_model = task_model @@ -242,6 +243,16 @@ class AssetWidget(QtWidgets.QWidget): def refresh(self): self._refresh_model() + def _refresh_tasks(self): + tasks = [] + selected = self.get_selected_assets() + if len(selected) == 1: + asset = self.db.find_one({ + "_id": selected[0], "type": "asset" + }) + tasks = asset['data'].get('tasks', []) + self.task_model.set_tasks(tasks) + def get_active_asset(self): """Return the asset id the current asset.""" current = self.view.currentIndex() From b4115f2867d9564f6074887ab2cdf8f738968916 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:27:29 +0200 Subject: [PATCH 18/28] getting tasks from asset is more secure --- pype/standalonepublish/widgets/widget_asset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index 92a4d1d88f..4b27ba808e 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -250,7 +250,8 @@ class AssetWidget(QtWidgets.QWidget): asset = self.db.find_one({ "_id": selected[0], "type": "asset" }) - tasks = asset['data'].get('tasks', []) + if asset: + tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) def get_active_asset(self): From 113bd52638b20e5211b07984109024fb5d962257 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:27:40 +0200 Subject: [PATCH 19/28] collect data also return task --- pype/standalonepublish/widgets/widget_asset.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index 4b27ba808e..41163e13d6 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -201,10 +201,17 @@ class AssetWidget(QtWidgets.QWidget): def collect_data(self): project = self.db.find_one({'type': 'project'}) asset = self.db.find_one({'_id': self.get_active_asset()}) + + try: + index = self.task_view.selectedIndexes()[0] + task = self.task_model.itemData(index)[0] + except Exception: + task = None data = { 'project': project['name'], 'asset': asset['name'], - 'parents': self.get_parents(asset) + 'parents': self.get_parents(asset), + 'task': task } return data From 72b8f24c85056745c589e55f45ba445f43cd5217 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:28:06 +0200 Subject: [PATCH 20/28] fixed subset name in collect_data --- pype/plugins/standalonepublish/publish/collect_context.py | 2 +- pype/standalonepublish/widgets/widget_family.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/standalonepublish/publish/collect_context.py b/pype/plugins/standalonepublish/publish/collect_context.py index cbe9df1ef6..2f3ca1ca27 100644 --- a/pype/plugins/standalonepublish/publish/collect_context.py +++ b/pype/plugins/standalonepublish/publish/collect_context.py @@ -55,7 +55,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): instance = context.create_instance(subset) instance.data.update({ - "subset": family + subset, + "subset": subset, "asset": asset_name, "label": family + subset, "name": family + subset, diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 1029f71593..78388d17d8 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -119,7 +119,7 @@ class FamilyWidget(QtWidgets.QWidget): family = plugin.family.rsplit(".", 1)[-1] data = { 'family': family, - 'subset': self.input_subset.text(), + 'subset': self.input_result.text(), 'version': self.version_spinbox.value() } return data From 39902e50a0e7abeb2b1a760b6b271bf113de1456 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 May 2019 17:51:54 +0200 Subject: [PATCH 21/28] remove echo label under assets --- pype/standalonepublish/app.py | 33 ++----------------- .../widgets/widget_family.py | 8 ++--- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/pype/standalonepublish/app.py b/pype/standalonepublish/app.py index 5d3bfd0505..da5fbbba10 100644 --- a/pype/standalonepublish/app.py +++ b/pype/standalonepublish/app.py @@ -39,19 +39,9 @@ class Window(QtWidgets.QDialog): # Validators self.valid_parent = False - # statusbar - added under asset_widget - label_message = QtWidgets.QLabel() - label_message.setFixedHeight(20) - # assets widget - widget_assets_wrap = QtWidgets.QWidget() - widget_assets_wrap.setContentsMargins(0, 0, 0, 0) widget_assets = AssetWidget(self) - layout_assets = QtWidgets.QVBoxLayout(widget_assets_wrap) - layout_assets.addWidget(widget_assets) - layout_assets.addWidget(label_message) - # family widget widget_family = FamilyWidget(self) @@ -66,10 +56,10 @@ class Window(QtWidgets.QDialog): QtWidgets.QSizePolicy.Expanding ) body.setOrientation(QtCore.Qt.Horizontal) - body.addWidget(widget_assets_wrap) + body.addWidget(widget_assets) body.addWidget(widget_family) body.addWidget(widget_components) - body.setStretchFactor(body.indexOf(widget_assets_wrap), 2) + body.setStretchFactor(body.indexOf(widget_assets), 2) body.setStretchFactor(body.indexOf(widget_family), 3) body.setStretchFactor(body.indexOf(widget_components), 5) @@ -81,13 +71,10 @@ class Window(QtWidgets.QDialog): # signals widget_assets.selection_changed.connect(self.on_asset_changed) - self.label_message = label_message self.widget_assets = widget_assets self.widget_family = widget_family self.widget_components = widget_components - self.echo("Connected to Database") - # on start self.on_start() @@ -130,22 +117,6 @@ class Window(QtWidgets.QDialog): parents.append(parent['name']) return parents - def echo(self, message): - ''' Shows message in label that disappear in 5s - :param message: Message that will be displayed - :type message: str - ''' - self.label_message.setText(str(message)) - def clear_text(): - ''' Helps prevent crash if this Window object - is deleted before 5s passed - ''' - try: - self.label_message.set_text("") - except: - pass - QtCore.QTimer.singleShot(5000, lambda: clear_text()) - def on_asset_changed(self): '''Callback on asset selection changed diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 78388d17d8..63776b1df3 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -227,12 +227,12 @@ class FamilyWidget(QtWidgets.QWidget): self._build_menu(defaults) item.setData(ExistsRole, True) - self.echo("Ready ..") else: self._build_menu([]) item.setData(ExistsRole, False) if asset_name != self.NOT_SELECTED: - self.echo("'%s' not found .." % asset_name) + # TODO add logging into standalone_publish + print("'%s' not found .." % asset_name) self.on_version_refresh() @@ -339,10 +339,6 @@ class FamilyWidget(QtWidgets.QWidget): self.list_families.setCurrentItem(self.list_families.item(0)) - def echo(self, message): - if hasattr(self.parent_widget, 'echo'): - self.parent_widget.echo(message) - def schedule(self, func, time, channel="default"): try: self._jobs[channel].stop() From 1f0836b433db7b0e675040297819fd42cb0e6b85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 May 2019 17:55:00 +0200 Subject: [PATCH 22/28] tasks widget is shown only if are any tasks available --- pype/standalonepublish/widgets/widget_asset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index 41163e13d6..54b7f7db44 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -169,6 +169,7 @@ class AssetWidget(QtWidgets.QWidget): task_view = DeselectableTreeView() task_view.setIndentation(0) task_view.setHeaderHidden(True) + task_view.setVisible(False) task_model = TasksTemplateModel() task_view.setModel(task_model) @@ -260,6 +261,7 @@ class AssetWidget(QtWidgets.QWidget): if asset: tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) + self.task_view.setVisible(len(tasks)>0) def get_active_asset(self): """Return the asset id the current asset.""" From 0e48fa33993ae81a8bf2c68807cd3cdb34463c00 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 15 May 2019 16:59:04 +0100 Subject: [PATCH 23/28] fix collector to include the new data --- .../standalonepublish/publish/collect_context.py | 13 ++++++------- .../publish/integrate_ftrack_instances.py | 14 +++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/pype/plugins/standalonepublish/publish/collect_context.py b/pype/plugins/standalonepublish/publish/collect_context.py index cbe9df1ef6..6ac2dca936 100644 --- a/pype/plugins/standalonepublish/publish/collect_context.py +++ b/pype/plugins/standalonepublish/publish/collect_context.py @@ -55,10 +55,10 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): instance = context.create_instance(subset) instance.data.update({ - "subset": family + subset, + "subset": subset, "asset": asset_name, - "label": family + subset, - "name": family + subset, + "label": subset, + "name": subset, "family": family, "families": [family, 'ftrack'], }) @@ -74,10 +74,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): collections, remainder = clique.assemble(component['files']) if collections: self.log.debug(collections) - range = collections[0].format('{range}') - instance.data['startFrame'] = range.split('-')[0] - instance.data['endFrame'] = range.split('-')[1] - + instance.data['startFrame'] = component['startFrame'] + instance.data['endFrame'] = component['endFrame'] + instance.data['frameRate'] = component['frameRate'] instance.data["files"].append(component) instance.data["representations"].append(component) diff --git a/pype/plugins/standalonepublish/publish/integrate_ftrack_instances.py b/pype/plugins/standalonepublish/publish/integrate_ftrack_instances.py index 8d938bceb0..0dc9bb137c 100644 --- a/pype/plugins/standalonepublish/publish/integrate_ftrack_instances.py +++ b/pype/plugins/standalonepublish/publish/integrate_ftrack_instances.py @@ -57,19 +57,19 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "name": "thumbnail" # Default component name is "main". } elif comp['preview']: - if not instance.data.get('startFrameReview'): - instance.data['startFrameReview'] = instance.data['startFrame'] - if not instance.data.get('endFrameReview'): - instance.data['endFrameReview'] = instance.data['endFrame'] + if not comp.get('startFrameReview'): + comp['startFrameReview'] = comp['startFrame'] + if not comp.get('endFrameReview'): + comp['endFrameReview'] = instance.data['endFrame'] location = ft_session.query( 'Location where name is "ftrack.server"').one() component_data = { # Default component name is "main". "name": "ftrackreview-mp4", "metadata": {'ftr_meta': json.dumps({ - 'frameIn': int(instance.data['startFrameReview']), - 'frameOut': int(instance.data['endFrameReview']), - 'frameRate': 25.0})} + 'frameIn': int(comp['startFrameReview']), + 'frameOut': int(comp['endFrameReview']), + 'frameRate': float(comp['frameRate')]})} } else: component_data = { From 0c5f876137dbba24f2c7953419337108ecdf00a5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 May 2019 21:45:52 +0200 Subject: [PATCH 24/28] feat(nuke): adding callback to nuke for change color of Loader nodes regarding to last version --- pype/nuke/lib.py | 47 +++++++++++++++++++++++++ pype/plugins/nuke/load/load_sequence.py | 8 +++-- setup/nuke/nuke_path/menu.py | 8 ++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 46b1d6e4c8..20e7dfb210 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -29,6 +29,53 @@ def onScriptLoad(): nuke.tcl('load movWriter') +def checkInventoryVersions(): + """ + Actiual version idetifier of Loaded containers + + Any time this function is run it will check all nodes and filter only Loader nodes for its version. It will get all versions from database + and check if the node is having actual version. If not then it will color it to red. + + """ + + + # get all Loader nodes by avalon attribute metadata + for each in nuke.allNodes(): + if each.Class() == 'Read': + container = avalon.nuke.parse_container(each) + + if container: + node = container["_tool"] + avalon_knob_data = get_avalon_knob_data(node) + + # get representation from io + representation = io.find_one({ + "type": "representation", + "_id": io.ObjectId(avalon_knob_data["representation"]) + }) + + # Get start frame from version data + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + # check the available version and do match + # change color of node if not max verion + if version.get("name") not in [max_version]: + node["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + node["tile_color"].setValue(int("0x4ecd25ff", 16)) + + def writes_version_sync(): try: rootVersion = pype.get_version_from_path(nuke.root().name()) diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index b4e3cfb8b5..f03e0fc97e 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -128,11 +128,15 @@ class LoadSequence(api.Loader): # add additional metadata from the version to imprint to Avalon knob add_keys = ["startFrame", "endFrame", "handles", - "source", "colorspace", "author", "fps"] + "source", "colorspace", "author", "fps", "version"] data_imprint = {} for k in add_keys: - data_imprint.update({k: context["version"]['data'][k]}) + if k is 'version': + data_imprint.update({k: context["version"]['name']}) + else: + data_imprint.update({k: context["version"]['data'][k]}) + data_imprint.update({"objectName": read_name}) r["tile_color"].setValue(int("0x4ecd25ff", 16)) diff --git a/setup/nuke/nuke_path/menu.py b/setup/nuke/nuke_path/menu.py index 9a96a52850..4982513b78 100644 --- a/setup/nuke/nuke_path/menu.py +++ b/setup/nuke/nuke_path/menu.py @@ -1,5 +1,10 @@ -from pype.nuke.lib import writes_version_sync, onScriptLoad +from pype.nuke.lib import ( + writes_version_sync, + onScriptLoad, + checkInventoryVersions +) + import nuke from pypeapp import Logger @@ -8,5 +13,6 @@ log = Logger().get_logger(__name__, "nuke") nuke.addOnScriptSave(writes_version_sync) nuke.addOnScriptSave(onScriptLoad) +nuke.addOnScriptSave(checkInventoryVersions) log.info('Automatic syncing of write file knob to script version') From d4dccba24884520fb44b6406d2e47c8f9a7fab8d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 May 2019 17:32:43 +0200 Subject: [PATCH 25/28] fix(nuke): logging cleaning --- pype/nuke/__init__.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pype/nuke/__init__.py b/pype/nuke/__init__.py index 376e8f95b8..a2b1aeda6e 100644 --- a/pype/nuke/__init__.py +++ b/pype/nuke/__init__.py @@ -25,8 +25,6 @@ from pypeapp import Logger log = Logger().get_logger(__name__, "nuke") -# log = api.Logger.getLogger(__name__, "nuke") - AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") PARENT_DIR = os.path.dirname(__file__) @@ -38,9 +36,8 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "nuke", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "nuke", "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nuke", "inventory") -self = sys.modules[__name__] -self.nLogger = None +# registering pyblish gui regarding settings in presets if os.getenv("PYBLISH_GUI", None): pyblish.register_gui(os.getenv("PYBLISH_GUI", None)) @@ -66,6 +63,7 @@ class NukeHandler(logging.Handler): "fatal", "error" ]: + msg = self.format(record) nuke.message(msg) @@ -77,9 +75,6 @@ if nuke_handler.get_name() \ logging.getLogger().addHandler(nuke_handler) logging.getLogger().setLevel(logging.INFO) -if not self.nLogger: - self.nLogger = Logger - def reload_config(): """Attempt to reload pipeline at run-time. @@ -157,7 +152,7 @@ def uninstall(): def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle node passthrough states on instance toggles.""" - self.log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( + log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( instance, old_value, new_value)) from avalon.nuke import ( From 16182d41eba4cd5859f7a6d645635a9473759346 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 May 2019 17:34:33 +0200 Subject: [PATCH 26/28] feat(nuke): Load nkscript as precomp with version --- pype/plugins/nuke/load/load_script_precomp.py | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 pype/plugins/nuke/load/load_script_precomp.py diff --git a/pype/plugins/nuke/load/load_script_precomp.py b/pype/plugins/nuke/load/load_script_precomp.py new file mode 100644 index 0000000000..6fd76edd03 --- /dev/null +++ b/pype/plugins/nuke/load/load_script_precomp.py @@ -0,0 +1,170 @@ +from avalon import api, style, io +from pype.nuke.lib import get_avalon_knob_data +import nuke +import os +from pype.api import Logger +log = Logger().get_logger(__name__, "nuke") + + + +class LinkAsGroup(api.Loader): + """Copy the published file to be pasted at the desired location""" + + representations = ["nk"] + families = ["*"] + + label = "Load Precomp" + order = 10 + icon = "file" + color = style.colors.dark + + def load(self, context, name, namespace, data): + + from avalon.nuke import containerise + # for k, v in context.items(): + # log.info("key: `{}`, value: {}\n".format(k, v)) + version = context['version'] + version_data = version.get("data", {}) + + vname = version.get("name", None) + first = version_data.get("startFrame", None) + last = version_data.get("endFrame", None) + + # Fallback to asset name when namespace is None + if namespace is None: + namespace = context['asset']['name'] + + file = self.fname.replace("\\", "/") + self.log.info("file: {}\n".format(self.fname)) + + precomp_name = context["representation"]["context"]["subset"] + + # Set global in point to start frame (if in version.data) + start = context["version"]["data"].get("startFrame", None) + + # add additional metadata from the version to imprint to Avalon knob + add_keys = ["startFrame", "endFrame", "handles", + "source", "author", "fps"] + + data_imprint = { + "start_frame": start, + "fstart": first, + "fend": last, + "version": vname + } + for k in add_keys: + data_imprint.update({k: context["version"]['data'][k]}) + data_imprint.update({"objectName": precomp_name}) + + # group context is set to precomp, so back up one level. + nuke.endGroup() + + # P = nuke.nodes.LiveGroup("file {}".format(file)) + P = nuke.createNode( + "Precomp", + "file {}".format(file)) + + # Set colorspace defined in version data + colorspace = context["version"]["data"].get("colorspace", None) + self.log.info("colorspace: {}\n".format(colorspace)) + + + # ['version', 'file', 'reading', 'output', 'useOutput'] + + P["name"].setValue("{}_{}".format(name, namespace)) + P["useOutput"].setValue(True) + + with P: + # iterate trough all nodes in group node and find pype writes + writes = [n.name() for n in nuke.allNodes() + if n.Class() == "Write" + if get_avalon_knob_data(n)] + + # create panel for selecting output + panel_choices = " ".join(writes) + panel_label = "Select write node for output" + p = nuke.Panel("Select Write Node") + p.addEnumerationPulldown( + panel_label, panel_choices) + p.show() + P["output"].setValue(p.value(panel_label)) + + P["tile_color"].setValue(0xff0ff0ff) + + return containerise( + node=P, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + """Update the Loader's path + + Nuke automatically tries to reset some variables when changing + the loader's path to a new file. These automatic changes are to its + inputs: + + """ + + from avalon.nuke import ( + update_container + ) + + node = nuke.toNode(container['objectName']) + + root = api.get_representation_path(representation).replace("\\","/") + + # Get start frame from version data + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + updated_dict = {} + updated_dict.update({ + "representation": str(representation["_id"]), + "endFrame": version["data"].get("endFrame"), + "version": version.get("name"), + "colorspace": version["data"].get("colorspace"), + "source": version["data"].get("source"), + "handles": version["data"].get("handles"), + "fps": version["data"].get("fps"), + "author": version["data"].get("author"), + "outputDir": version["data"].get("outputDir"), + }) + + # Update the imprinted representation + update_container( + node, + updated_dict + ) + + node["file"].setValue(root) + + # change color of node + if version.get("name") not in [max_version]: + node["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + node["tile_color"].setValue(int("0xff0ff0ff", 16)) + + log.info("udated to version: {}".format(version.get("name"))) + + + def remove(self, container): + from avalon.nuke import viewer_update_and_undo_stop + node = nuke.toNode(container['objectName']) + with viewer_update_and_undo_stop(): + nuke.delete(node) From 4f4a45c54b201cd69ec056622eeeee351a94043e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 May 2019 11:21:43 +0200 Subject: [PATCH 27/28] fix(premiere): removing message_window from pype.api and adding link to the message into premiere --- pype/api.py | 5 ----- pype/premiere/__init__.py | 6 ++++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pype/api.py b/pype/api.py index fcdcbce82b..0acb80e383 100644 --- a/pype/api.py +++ b/pype/api.py @@ -46,8 +46,6 @@ from .lib import ( get_data_hierarchical_attr ) -from .widgets.message_window import message - __all__ = [ # plugin classes "Extractor", @@ -89,7 +87,4 @@ __all__ = [ "Colorspace", "Dataflow", - # QtWidgets - "message" - ] diff --git a/pype/premiere/__init__.py b/pype/premiere/__init__.py index 74ce106de2..cc5abe115e 100644 --- a/pype/premiere/__init__.py +++ b/pype/premiere/__init__.py @@ -7,6 +7,8 @@ from pyblish import api as pyblish from pypeapp import Logger from .. import api +from ..widgets.message_window import message + import requests log = Logger().get_logger(__name__, "premiere") @@ -42,7 +44,7 @@ def request_aport(url_path, data={}): return req except Exception as e: - api.message(title="Premiere Aport Server", + message(title="Premiere Aport Server", message="Before you can run Premiere, start Aport Server. \n Error: {}".format( e), level="critical") @@ -99,7 +101,7 @@ def install(): # synchronize extensions extensions_sync() - api.message(title="pyblish_paths", message=str(reg_paths), level="info") + message(title="pyblish_paths", message=str(reg_paths), level="info") def uninstall(): From 8c2db60b26074f66578b8f91eec0651bfa2a7992 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 May 2019 16:21:47 +0200 Subject: [PATCH 28/28] (hotfix)/ added stagingDir to standalone publisher --- .../widgets/widget_component_item.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_component_item.py b/pype/standalonepublish/widgets/widget_component_item.py index 43aa54a955..a58a292ec5 100644 --- a/pype/standalonepublish/widgets/widget_component_item.py +++ b/pype/standalonepublish/widgets/widget_component_item.py @@ -284,11 +284,19 @@ class ComponentItem(QtWidgets.QFrame): self.preview.change_checked(hover) def collect_data(self): + in_files = self.in_data['files'] + staging_dir = os.path.dirname(in_files[0]) + + files = [os.path.basename(file) for file in in_files] + if len(files) == 1: + files = files[0] + data = { 'ext': self.in_data['ext'], 'label': self.name.text(), - 'representation': self.input_repre.text(), - 'files': self.in_data['files'], + 'name': self.input_repre.text(), + 'stagingDir': staging_dir, + 'files': files, 'thumbnail': self.is_thumbnail(), 'preview': self.is_preview() }