mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into feature/PYPE-539_improve_sync_logging
This commit is contained in:
commit
2b619c86c7
44 changed files with 1220 additions and 418 deletions
|
|
@ -1,129 +0,0 @@
|
|||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
from pype.vendor import ftrack_api
|
||||
from pype.ftrack import BaseAction
|
||||
|
||||
|
||||
class AssetDelete(BaseAction):
|
||||
'''Custom action.'''
|
||||
|
||||
#: Action identifier.
|
||||
identifier = 'asset.delete'
|
||||
#: Action label.
|
||||
label = 'Asset Delete'
|
||||
|
||||
def discover(self, session, entities, event):
|
||||
''' Validation '''
|
||||
|
||||
if (
|
||||
len(entities) != 1 or
|
||||
entities[0].entity_type not in ['Shot', 'Asset Build']
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def interface(self, session, entities, event):
|
||||
|
||||
if not event['data'].get('values', {}):
|
||||
entity = entities[0]
|
||||
|
||||
items = []
|
||||
for asset in entity['assets']:
|
||||
# get asset name for label
|
||||
label = 'None'
|
||||
if asset['name']:
|
||||
label = asset['name']
|
||||
|
||||
items.append({
|
||||
'label': label,
|
||||
'name': label,
|
||||
'value': False,
|
||||
'type': 'boolean'
|
||||
})
|
||||
|
||||
if len(items) < 1:
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'There are no assets to delete'
|
||||
}
|
||||
|
||||
return items
|
||||
|
||||
def launch(self, session, entities, event):
|
||||
|
||||
entity = entities[0]
|
||||
# if values were set remove those items
|
||||
if 'values' in event['data']:
|
||||
values = event['data']['values']
|
||||
# get list of assets to delete from form
|
||||
to_delete = []
|
||||
for key in values:
|
||||
if values[key]:
|
||||
to_delete.append(key)
|
||||
# delete them by name
|
||||
for asset in entity['assets']:
|
||||
if asset['name'] in to_delete:
|
||||
session.delete(asset)
|
||||
try:
|
||||
session.commit()
|
||||
except Exception:
|
||||
session.rollback()
|
||||
raise
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Asset deleted.'
|
||||
}
|
||||
|
||||
|
||||
def register(session, plugins_presets={}):
|
||||
'''Register action. Called when used as an event plugin.'''
|
||||
|
||||
# Validate that session is an instance of ftrack_api.Session. If not,
|
||||
# assume that register is being called from an old or incompatible API and
|
||||
# return without doing anything.
|
||||
if not isinstance(session, ftrack_api.session.Session):
|
||||
return
|
||||
|
||||
AssetDelete(session, plugins_presets).register()
|
||||
|
||||
|
||||
def main(arguments=None):
|
||||
'''Set up logging and register action.'''
|
||||
if arguments is None:
|
||||
arguments = []
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
# Allow setting of logging level from arguments.
|
||||
loggingLevels = {}
|
||||
for level in (
|
||||
logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
|
||||
logging.ERROR, logging.CRITICAL
|
||||
):
|
||||
loggingLevels[logging.getLevelName(level).lower()] = level
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbosity',
|
||||
help='Set the logging output verbosity.',
|
||||
choices=loggingLevels.keys(),
|
||||
default='info'
|
||||
)
|
||||
namespace = parser.parse_args(arguments)
|
||||
|
||||
# Set up basic logging
|
||||
logging.basicConfig(level=loggingLevels[namespace.verbosity])
|
||||
|
||||
session = ftrack_api.Session()
|
||||
register(session)
|
||||
|
||||
# Wait for events
|
||||
logging.info(
|
||||
'Registered actions and listening for events. Use Ctrl-C to abort.'
|
||||
)
|
||||
session.event_hub.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
|
|
@ -8,6 +8,7 @@ from pype.ftrack.lib.io_nonsingleton import DbConnector
|
|||
class AttributesRemapper(BaseAction):
|
||||
'''Edit meta data action.'''
|
||||
|
||||
ignore_me = True
|
||||
#: Action identifier.
|
||||
identifier = 'attributes.remapper'
|
||||
#: Action label.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from pype.ftrack import BaseAction
|
|||
|
||||
|
||||
class CustomAttributeDoctor(BaseAction):
|
||||
|
||||
ignore_me = True
|
||||
#: Action identifier.
|
||||
identifier = 'custom.attributes.doctor'
|
||||
#: Action label.
|
||||
|
|
@ -26,7 +28,9 @@ class CustomAttributeDoctor(BaseAction):
|
|||
hierarchical_ca = ['handleStart', 'handleEnd', 'frameStart', 'frameEnd']
|
||||
hierarchical_alternatives = {
|
||||
'handleStart': 'handles',
|
||||
'handleEnd': 'handles'
|
||||
'handleEnd': 'handles',
|
||||
"frameStart": "fstart",
|
||||
"frameEnd": "fend"
|
||||
}
|
||||
|
||||
# Roles for new custom attributes
|
||||
|
|
|
|||
|
|
@ -1,93 +0,0 @@
|
|||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
from pype.vendor import ftrack_api
|
||||
from pype.ftrack import BaseAction
|
||||
|
||||
|
||||
class VersionsCleanup(BaseAction):
|
||||
'''Custom action.'''
|
||||
|
||||
# Action identifier
|
||||
identifier = 'versions.cleanup'
|
||||
# Action label
|
||||
label = 'Versions cleanup'
|
||||
|
||||
def discover(self, session, entities, event):
|
||||
''' Validation '''
|
||||
|
||||
# Only 1 AssetVersion is allowed
|
||||
if len(entities) != 1 or entities[0].entity_type != 'AssetVersion':
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def launch(self, session, entities, event):
|
||||
|
||||
entity = entities[0]
|
||||
|
||||
# Go through all versions in asset
|
||||
for version in entity['asset']['versions']:
|
||||
if not version['is_published']:
|
||||
session.delete(version)
|
||||
try:
|
||||
session.commit()
|
||||
except Exception:
|
||||
session.rollback()
|
||||
raise
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Hidden versions were removed'
|
||||
}
|
||||
|
||||
|
||||
def register(session, plugins_presets={}):
|
||||
'''Register action. Called when used as an event plugin.'''
|
||||
|
||||
# Validate that session is an instance of ftrack_api.Session. If not,
|
||||
# assume that register is being called from an old or incompatible API and
|
||||
# return without doing anything.
|
||||
if not isinstance(session, ftrack_api.session.Session):
|
||||
return
|
||||
|
||||
VersionsCleanup(session, plugins_presets).register()
|
||||
|
||||
|
||||
def main(arguments=None):
|
||||
'''Set up logging and register action.'''
|
||||
if arguments is None:
|
||||
arguments = []
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
# Allow setting of logging level from arguments.
|
||||
loggingLevels = {}
|
||||
for level in (
|
||||
logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
|
||||
logging.ERROR, logging.CRITICAL
|
||||
):
|
||||
loggingLevels[logging.getLevelName(level).lower()] = level
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbosity',
|
||||
help='Set the logging output verbosity.',
|
||||
choices=loggingLevels.keys(),
|
||||
default='info'
|
||||
)
|
||||
namespace = parser.parse_args(arguments)
|
||||
|
||||
# Set up basic logging
|
||||
logging.basicConfig(level=loggingLevels[namespace.verbosity])
|
||||
|
||||
session = ftrack_api.Session()
|
||||
register(session)
|
||||
|
||||
# Wait for events
|
||||
logging.info(
|
||||
'Registered actions and listening for events. Use Ctrl-C to abort.'
|
||||
)
|
||||
session.event_hub.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
|
|
@ -107,7 +107,7 @@ class JobKiller(BaseAction):
|
|||
'Changing Job ({}) status: {} -> failed'
|
||||
).format(job['id'], job['status']))
|
||||
except Exception:
|
||||
self.warning.debug((
|
||||
self.log.warning.debug((
|
||||
'Changing Job ({}) has failed'
|
||||
).format(job['id']))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
from pype.vendor import ftrack_api
|
||||
from pype.ftrack import BaseAction
|
||||
|
||||
|
||||
class SetVersion(BaseAction):
|
||||
'''Custom action.'''
|
||||
|
||||
#: Action identifier.
|
||||
identifier = 'version.set'
|
||||
#: Action label.
|
||||
label = 'Version Set'
|
||||
|
||||
def discover(self, session, entities, event):
|
||||
''' Validation '''
|
||||
|
||||
# Only 1 AssetVersion is allowed
|
||||
if len(entities) != 1 or entities[0].entity_type != 'AssetVersion':
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def interface(self, session, entities, event):
|
||||
|
||||
if not event['data'].get('values', {}):
|
||||
entity = entities[0]
|
||||
|
||||
# Get actual version of asset
|
||||
act_ver = entity['version']
|
||||
# Set form
|
||||
items = [{
|
||||
'label': 'Version number',
|
||||
'type': 'number',
|
||||
'name': 'version_number',
|
||||
'value': act_ver
|
||||
}]
|
||||
|
||||
return items
|
||||
|
||||
def launch(self, session, entities, event):
|
||||
|
||||
entity = entities[0]
|
||||
|
||||
# Do something with the values or return a new form.
|
||||
values = event['data'].get('values', {})
|
||||
# Default is action True
|
||||
scs = False
|
||||
|
||||
if not values['version_number']:
|
||||
msg = 'You didn\'t enter any version.'
|
||||
elif int(values['version_number']) <= 0:
|
||||
msg = 'Negative or zero version is not valid.'
|
||||
else:
|
||||
try:
|
||||
entity['version'] = values['version_number']
|
||||
session.commit()
|
||||
msg = 'Version was changed to v{0}'.format(
|
||||
values['version_number']
|
||||
)
|
||||
scs = True
|
||||
except Exception as e:
|
||||
msg = 'Unexpected error occurs during version set ({})'.format(
|
||||
str(e)
|
||||
)
|
||||
|
||||
return {
|
||||
'success': scs,
|
||||
'message': msg
|
||||
}
|
||||
|
||||
|
||||
def register(session, plugins_presets={}):
|
||||
'''Register action. Called when used as an event plugin.'''
|
||||
|
||||
# Validate that session is an instance of ftrack_api.Session. If not,
|
||||
# assume that register is being called from an old or incompatible API and
|
||||
# return without doing anything.
|
||||
if not isinstance(session, ftrack_api.session.Session):
|
||||
return
|
||||
|
||||
SetVersion(session, plugins_presets).register()
|
||||
|
||||
|
||||
def main(arguments=None):
|
||||
'''Set up logging and register action.'''
|
||||
if arguments is None:
|
||||
arguments = []
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
# Allow setting of logging level from arguments.
|
||||
loggingLevels = {}
|
||||
for level in (
|
||||
logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
|
||||
logging.ERROR, logging.CRITICAL
|
||||
):
|
||||
loggingLevels[logging.getLevelName(level).lower()] = level
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbosity',
|
||||
help='Set the logging output verbosity.',
|
||||
choices=loggingLevels.keys(),
|
||||
default='info'
|
||||
)
|
||||
namespace = parser.parse_args(arguments)
|
||||
|
||||
# Set up basic logging
|
||||
logging.basicConfig(level=loggingLevels[namespace.verbosity])
|
||||
|
||||
session = ftrack_api.Session()
|
||||
register(session)
|
||||
|
||||
# Wait for events
|
||||
logging.info(
|
||||
'Registered actions and listening for events. Use Ctrl-C to abort.'
|
||||
)
|
||||
session.event_hub.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
|
|
@ -21,7 +21,7 @@ class SyncHierarchicalAttrs(BaseAction):
|
|||
identifier = 'sync.hierarchical.attrs'
|
||||
#: Action label.
|
||||
label = "Pype Admin"
|
||||
variant = '- Sync Hier Attrs (server)'
|
||||
variant = '- Sync Hier Attrs (Server)'
|
||||
#: Action description.
|
||||
description = 'Synchronize hierarchical attributes'
|
||||
#: Icon
|
||||
|
|
|
|||
0
pype/logging/gui/__init__.py
Normal file
0
pype/logging/gui/__init__.py
Normal file
37
pype/logging/gui/app.py
Normal file
37
pype/logging/gui/app.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from Qt import QtWidgets, QtCore
|
||||
from .widgets import LogsWidget, LogDetailWidget
|
||||
from pypeapp import style
|
||||
|
||||
|
||||
class LogsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(LogsWindow, self).__init__(parent)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.resize(1200, 800)
|
||||
logs_widget = LogsWidget(parent=self)
|
||||
log_detail = LogDetailWidget(parent=self)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
log_splitter = QtWidgets.QSplitter()
|
||||
log_splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
log_splitter.addWidget(logs_widget)
|
||||
log_splitter.addWidget(log_detail)
|
||||
log_splitter.setStretchFactor(0, 65)
|
||||
log_splitter.setStretchFactor(1, 35)
|
||||
|
||||
main_layout.addWidget(log_splitter)
|
||||
|
||||
self.logs_widget = logs_widget
|
||||
self.log_detail = log_detail
|
||||
|
||||
self.setLayout(main_layout)
|
||||
self.setWindowTitle("Logs")
|
||||
|
||||
self.logs_widget.active_changed.connect(self.on_selection_changed)
|
||||
|
||||
def on_selection_changed(self):
|
||||
index = self.logs_widget.selected_log()
|
||||
node = index.data(self.logs_widget.model.NodeRole)
|
||||
self.log_detail.set_detail(node)
|
||||
94
pype/logging/gui/lib.py
Normal file
94
pype/logging/gui/lib.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import contextlib
|
||||
from Qt import QtCore
|
||||
|
||||
|
||||
def _iter_model_rows(
|
||||
model, column, include_root=False
|
||||
):
|
||||
"""Iterate over all row indices in a model"""
|
||||
indices = [QtCore.QModelIndex()] # start iteration at root
|
||||
|
||||
for index in indices:
|
||||
# Add children to the iterations
|
||||
child_rows = model.rowCount(index)
|
||||
for child_row in range(child_rows):
|
||||
child_index = model.index(child_row, column, index)
|
||||
indices.append(child_index)
|
||||
|
||||
if not include_root and not index.isValid():
|
||||
continue
|
||||
|
||||
yield index
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def preserve_states(
|
||||
tree_view, column=0, role=None,
|
||||
preserve_expanded=True, preserve_selection=True,
|
||||
expanded_role=QtCore.Qt.DisplayRole, selection_role=QtCore.Qt.DisplayRole
|
||||
|
||||
):
|
||||
"""Preserves row selection in QTreeView by column's data role.
|
||||
|
||||
This function is created to maintain the selection status of
|
||||
the model items. When refresh is triggered the items which are expanded
|
||||
will stay expanded and vise versa.
|
||||
|
||||
tree_view (QWidgets.QTreeView): the tree view nested in the application
|
||||
column (int): the column to retrieve the data from
|
||||
role (int): the role which dictates what will be returned
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
# When `role` is set then override both expanded and selection roles
|
||||
if role:
|
||||
expanded_role = role
|
||||
selection_role = role
|
||||
|
||||
model = tree_view.model()
|
||||
selection_model = tree_view.selectionModel()
|
||||
flags = selection_model.Select | selection_model.Rows
|
||||
|
||||
expanded = set()
|
||||
|
||||
if preserve_expanded:
|
||||
for index in _iter_model_rows(
|
||||
model, column=column, include_root=False
|
||||
):
|
||||
if tree_view.isExpanded(index):
|
||||
value = index.data(expanded_role)
|
||||
expanded.add(value)
|
||||
|
||||
selected = None
|
||||
|
||||
if preserve_selection:
|
||||
selected_rows = selection_model.selectedRows()
|
||||
if selected_rows:
|
||||
selected = set(row.data(selection_role) for row in selected_rows)
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if expanded:
|
||||
for index in _iter_model_rows(
|
||||
model, column=0, include_root=False
|
||||
):
|
||||
value = index.data(expanded_role)
|
||||
is_expanded = value in expanded
|
||||
# skip if new index was created meanwhile
|
||||
if is_expanded is None:
|
||||
continue
|
||||
tree_view.setExpanded(index, is_expanded)
|
||||
|
||||
if selected:
|
||||
# Go through all indices, select the ones with similar data
|
||||
for index in _iter_model_rows(
|
||||
model, column=column, include_root=False
|
||||
):
|
||||
value = index.data(selection_role)
|
||||
state = value in selected
|
||||
if state:
|
||||
tree_view.scrollTo(index) # Ensure item is visible
|
||||
selection_model.select(index, flags)
|
||||
169
pype/logging/gui/models.py
Normal file
169
pype/logging/gui/models.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import os
|
||||
from Qt import QtCore
|
||||
from pypeapp import Logger
|
||||
from pypeapp.lib.log import _bootstrap_mongo_log
|
||||
|
||||
log = Logger().get_logger("LogModel", "LoggingModule")
|
||||
|
||||
|
||||
class LogModel(QtCore.QAbstractItemModel):
|
||||
COLUMNS = [
|
||||
"user",
|
||||
"host",
|
||||
"lineNumber",
|
||||
"method",
|
||||
"module",
|
||||
"fileName",
|
||||
"loggerName",
|
||||
"message",
|
||||
"level",
|
||||
"timestamp",
|
||||
]
|
||||
|
||||
colums_mapping = {
|
||||
"user": "User",
|
||||
"host": "Host",
|
||||
"lineNumber": "Line n.",
|
||||
"method": "Method",
|
||||
"module": "Module",
|
||||
"fileName": "File name",
|
||||
"loggerName": "Logger name",
|
||||
"message": "Message",
|
||||
"level": "Level",
|
||||
"timestamp": "Timestamp",
|
||||
}
|
||||
|
||||
NodeRole = QtCore.Qt.UserRole + 1
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(LogModel, self).__init__(parent)
|
||||
self._root_node = Node()
|
||||
|
||||
collection = os.environ.get('PYPE_LOG_MONGO_COL')
|
||||
database = _bootstrap_mongo_log()
|
||||
self.dbcon = None
|
||||
if collection in database.list_collection_names():
|
||||
self.dbcon = database[collection]
|
||||
|
||||
def add_log(self, log):
|
||||
node = Node(log)
|
||||
self._root_node.add_child(node)
|
||||
|
||||
def refresh(self):
|
||||
self.clear()
|
||||
self.beginResetModel()
|
||||
if self.dbcon:
|
||||
result = self.dbcon.find({})
|
||||
for item in result:
|
||||
self.add_log(item)
|
||||
self.endResetModel()
|
||||
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
|
||||
node = index.internalPointer()
|
||||
column = index.column()
|
||||
|
||||
key = self.COLUMNS[column]
|
||||
if key == "timestamp":
|
||||
return str(node.get(key, None))
|
||||
return node.get(key, None)
|
||||
|
||||
if role == self.NodeRole:
|
||||
return index.internalPointer()
|
||||
|
||||
def index(self, row, column, parent):
|
||||
"""Return index for row/column under parent"""
|
||||
|
||||
if not parent.isValid():
|
||||
parent_node = self._root_node
|
||||
else:
|
||||
parent_node = parent.internalPointer()
|
||||
|
||||
child_item = parent_node.child(row)
|
||||
if child_item:
|
||||
return self.createIndex(row, column, child_item)
|
||||
else:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
def rowCount(self, parent):
|
||||
node = self._root_node
|
||||
if parent.isValid():
|
||||
node = parent.internalPointer()
|
||||
return node.childCount()
|
||||
|
||||
def columnCount(self, parent):
|
||||
return len(self.COLUMNS)
|
||||
|
||||
def parent(self, index):
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if section < len(self.COLUMNS):
|
||||
key = self.COLUMNS[section]
|
||||
return self.colums_mapping.get(key, key)
|
||||
|
||||
super(LogModel, self).headerData(section, orientation, role)
|
||||
|
||||
def flags(self, index):
|
||||
return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
|
||||
|
||||
def clear(self):
|
||||
self.beginResetModel()
|
||||
self._root_node = Node()
|
||||
self.endResetModel()
|
||||
|
||||
|
||||
class Node(dict):
|
||||
"""A node that can be represented in a tree view.
|
||||
|
||||
The node can store data just like a dictionary.
|
||||
|
||||
>>> data = {"name": "John", "score": 10}
|
||||
>>> node = Node(data)
|
||||
>>> assert node["name"] == "John"
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, data=None):
|
||||
super(Node, self).__init__()
|
||||
|
||||
self._children = list()
|
||||
self._parent = None
|
||||
|
||||
if data is not None:
|
||||
assert isinstance(data, dict)
|
||||
self.update(data)
|
||||
|
||||
def childCount(self):
|
||||
return len(self._children)
|
||||
|
||||
def child(self, row):
|
||||
if row >= len(self._children):
|
||||
log.warning("Invalid row as child: {0}".format(row))
|
||||
return
|
||||
|
||||
return self._children[row]
|
||||
|
||||
def children(self):
|
||||
return self._children
|
||||
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
def row(self):
|
||||
"""
|
||||
Returns:
|
||||
int: Index of this node under parent"""
|
||||
if self._parent is not None:
|
||||
siblings = self.parent().children()
|
||||
return siblings.index(self)
|
||||
|
||||
def add_child(self, child):
|
||||
"""Add a child to this node"""
|
||||
child._parent = self
|
||||
self._children.append(child)
|
||||
426
pype/logging/gui/widgets.py
Normal file
426
pype/logging/gui/widgets.py
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
import datetime
|
||||
import inspect
|
||||
from Qt import QtCore, QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QVariant
|
||||
from .models import LogModel
|
||||
|
||||
from .lib import preserve_states
|
||||
|
||||
|
||||
class SearchComboBox(QtWidgets.QComboBox):
|
||||
"""Searchable ComboBox with empty placeholder value as first value"""
|
||||
|
||||
def __init__(self, parent=None, placeholder=""):
|
||||
super(SearchComboBox, self).__init__(parent)
|
||||
|
||||
self.setEditable(True)
|
||||
self.setInsertPolicy(self.NoInsert)
|
||||
self.lineEdit().setPlaceholderText(placeholder)
|
||||
|
||||
# Apply completer settings
|
||||
completer = self.completer()
|
||||
completer.setCompletionMode(completer.PopupCompletion)
|
||||
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
|
||||
# Force style sheet on popup menu
|
||||
# It won't take the parent stylesheet for some reason
|
||||
# todo: better fix for completer popup stylesheet
|
||||
if parent:
|
||||
popup = completer.popup()
|
||||
popup.setStyleSheet(parent.styleSheet())
|
||||
|
||||
self.currentIndexChanged.connect(self.onIndexChange)
|
||||
|
||||
def onIndexChange(self, index):
|
||||
print(index)
|
||||
|
||||
def populate(self, items):
|
||||
self.clear()
|
||||
self.addItems([""]) # ensure first item is placeholder
|
||||
self.addItems(items)
|
||||
|
||||
def get_valid_value(self):
|
||||
"""Return the current text if it's a valid value else None
|
||||
|
||||
Note: The empty placeholder value is valid and returns as ""
|
||||
|
||||
"""
|
||||
|
||||
text = self.currentText()
|
||||
lookup = set(self.itemText(i) for i in range(self.count()))
|
||||
if text not in lookup:
|
||||
return None
|
||||
|
||||
return text
|
||||
|
||||
class CheckableComboBox2(QtWidgets.QComboBox):
|
||||
def __init__(self, parent=None):
|
||||
super(CheckableComboBox, self).__init__(parent)
|
||||
self.view().pressed.connect(self.handleItemPressed)
|
||||
self._changed = False
|
||||
|
||||
def handleItemPressed(self, index):
|
||||
item = self.model().itemFromIndex(index)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self._changed = True
|
||||
|
||||
def hidePopup(self):
|
||||
if not self._changed:
|
||||
super(CheckableComboBox, self).hidePopup()
|
||||
self._changed = False
|
||||
|
||||
def itemChecked(self, index):
|
||||
item = self.model().item(index, self.modelColumn())
|
||||
return item.checkState() == QtCore.Qt.Checked
|
||||
|
||||
def setItemChecked(self, index, checked=True):
|
||||
item = self.model().item(index, self.modelColumn())
|
||||
if checked:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
|
||||
class SelectableMenu(QtWidgets.QMenu):
|
||||
|
||||
selection_changed = QtCore.Signal()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
action = self.activeAction()
|
||||
if action and action.isEnabled():
|
||||
action.trigger()
|
||||
self.selection_changed.emit()
|
||||
else:
|
||||
super(SelectableMenu, self).mouseReleaseEvent(event)
|
||||
|
||||
class CustomCombo(QtWidgets.QWidget):
|
||||
|
||||
selection_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, title, parent=None):
|
||||
super(CustomCombo, self).__init__(parent)
|
||||
toolbutton = QtWidgets.QToolButton(self)
|
||||
toolbutton.setText(title)
|
||||
|
||||
toolmenu = SelectableMenu(self)
|
||||
|
||||
toolbutton.setMenu(toolmenu)
|
||||
toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(toolbutton)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
# toolmenu.selection_changed.connect(self.on_selection_changed)
|
||||
toolmenu.selection_changed.connect(self.selection_changed)
|
||||
|
||||
self.toolbutton = toolbutton
|
||||
self.toolmenu = toolmenu
|
||||
self.main_layout = layout
|
||||
|
||||
def populate(self, items):
|
||||
self.toolmenu.clear()
|
||||
self.addItems(items)
|
||||
|
||||
def addItems(self, items):
|
||||
for item in items:
|
||||
action = self.toolmenu.addAction(item)
|
||||
action.setCheckable(True)
|
||||
action.setChecked(True)
|
||||
self.toolmenu.addAction(action)
|
||||
|
||||
def items(self):
|
||||
for action in self.toolmenu.actions():
|
||||
yield action
|
||||
|
||||
|
||||
class CheckableComboBox(QtWidgets.QComboBox):
|
||||
def __init__(self, parent=None):
|
||||
super(CheckableComboBox, self).__init__(parent)
|
||||
|
||||
view = QtWidgets.QTreeView()
|
||||
view.header().hide()
|
||||
view.setRootIsDecorated(False)
|
||||
|
||||
model = QtGui.QStandardItemModel()
|
||||
|
||||
view.pressed.connect(self.handleItemPressed)
|
||||
self._changed = False
|
||||
|
||||
self.setView(view)
|
||||
self.setModel(model)
|
||||
|
||||
self.view = view
|
||||
self.model = model
|
||||
|
||||
def handleItemPressed(self, index):
|
||||
item = self.model.itemFromIndex(index)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self._changed = True
|
||||
|
||||
def hidePopup(self):
|
||||
if not self._changed:
|
||||
super(CheckableComboBox, self).hidePopup()
|
||||
self._changed = False
|
||||
|
||||
def itemChecked(self, index):
|
||||
item = self.model.item(index, self.modelColumn())
|
||||
return item.checkState() == QtCore.Qt.Checked
|
||||
|
||||
def setItemChecked(self, index, checked=True):
|
||||
item = self.model.item(index, self.modelColumn())
|
||||
if checked:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
def addItems(self, items):
|
||||
for text, checked in items:
|
||||
text_item = QtGui.QStandardItem(text)
|
||||
checked_item = QtGui.QStandardItem()
|
||||
checked_item.setData(QVariant(checked), QtCore.Qt.CheckStateRole)
|
||||
self.model.appendRow([text_item, checked_item])
|
||||
|
||||
|
||||
class LogsWidget(QtWidgets.QWidget):
|
||||
"""A widget that lists the published subsets for an asset"""
|
||||
|
||||
active_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(LogsWidget, self).__init__(parent=parent)
|
||||
|
||||
model = LogModel()
|
||||
|
||||
filter_layout = QtWidgets.QHBoxLayout()
|
||||
|
||||
# user_filter = SearchComboBox(self, "Users")
|
||||
user_filter = CustomCombo("Users", self)
|
||||
users = model.dbcon.distinct("user")
|
||||
user_filter.populate(users)
|
||||
user_filter.selection_changed.connect(self.user_changed)
|
||||
|
||||
level_filter = CustomCombo("Levels", self)
|
||||
# levels = [(level, True) for level in model.dbcon.distinct("level")]
|
||||
levels = model.dbcon.distinct("level")
|
||||
level_filter.addItems(levels)
|
||||
|
||||
date_from_label = QtWidgets.QLabel("From:")
|
||||
date_filter_from = QtWidgets.QDateTimeEdit()
|
||||
|
||||
date_from_layout = QtWidgets.QVBoxLayout()
|
||||
date_from_layout.addWidget(date_from_label)
|
||||
date_from_layout.addWidget(date_filter_from)
|
||||
|
||||
# now = datetime.datetime.now()
|
||||
# QtCore.QDateTime(now.year, now.month, now.day, now.hour, now.minute, second = 0, msec = 0, timeSpec = 0)
|
||||
date_to_label = QtWidgets.QLabel("To:")
|
||||
date_filter_to = QtWidgets.QDateTimeEdit()
|
||||
|
||||
date_to_layout = QtWidgets.QVBoxLayout()
|
||||
date_to_layout.addWidget(date_to_label)
|
||||
date_to_layout.addWidget(date_filter_to)
|
||||
|
||||
filter_layout.addWidget(user_filter)
|
||||
filter_layout.addWidget(level_filter)
|
||||
|
||||
filter_layout.addLayout(date_from_layout)
|
||||
filter_layout.addLayout(date_to_layout)
|
||||
|
||||
view = QtWidgets.QTreeView(self)
|
||||
view.setAllColumnsShowFocus(True)
|
||||
|
||||
# # Set view delegates
|
||||
# time_delegate = PrettyTimeDelegate()
|
||||
# column = model.COLUMNS.index("time")
|
||||
# view.setItemDelegateForColumn(column, time_delegate)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(filter_layout)
|
||||
layout.addWidget(view)
|
||||
|
||||
view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
view.setSortingEnabled(True)
|
||||
view.sortByColumn(
|
||||
model.COLUMNS.index("timestamp"),
|
||||
QtCore.Qt.AscendingOrder
|
||||
)
|
||||
|
||||
view.setModel(model)
|
||||
|
||||
view.customContextMenuRequested.connect(self.on_context_menu)
|
||||
view.selectionModel().selectionChanged.connect(self.active_changed)
|
||||
# user_filter.connect()
|
||||
|
||||
# TODO remove if nothing will affect...
|
||||
# header = self.view.header()
|
||||
# # Enforce the columns to fit the data (purely cosmetic)
|
||||
# if Qt.__binding__ in ("PySide2", "PyQt5"):
|
||||
# header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
|
||||
# else:
|
||||
# header.setResizeMode(QtWidgets.QHeaderView.ResizeToContents)
|
||||
|
||||
# Set signals
|
||||
|
||||
# prepare
|
||||
model.refresh()
|
||||
|
||||
# Store to memory
|
||||
self.model = model
|
||||
self.view = view
|
||||
|
||||
self.user_filter = user_filter
|
||||
|
||||
def user_changed(self):
|
||||
for action in self.user_filter.items():
|
||||
print(action)
|
||||
|
||||
def on_context_menu(self, point):
|
||||
# TODO will be any actions? it's ready
|
||||
return
|
||||
|
||||
point_index = self.view.indexAt(point)
|
||||
if not point_index.isValid():
|
||||
return
|
||||
|
||||
# Get selected subsets without groups
|
||||
selection = self.view.selectionModel()
|
||||
rows = selection.selectedRows(column=0)
|
||||
|
||||
def selected_log(self):
|
||||
selection = self.view.selectionModel()
|
||||
rows = selection.selectedRows(column=0)
|
||||
if len(rows) == 1:
|
||||
return rows[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class LogDetailWidget(QtWidgets.QWidget):
|
||||
"""A Widget that display information about a specific version"""
|
||||
data_rows = [
|
||||
"user",
|
||||
"message",
|
||||
"level",
|
||||
"logname",
|
||||
"method",
|
||||
"module",
|
||||
"fileName",
|
||||
"lineNumber",
|
||||
"host",
|
||||
"timestamp"
|
||||
]
|
||||
|
||||
html_text = u"""
|
||||
<h3>{user} - {timestamp}</h3>
|
||||
<b>User</b><br>{user}<br>
|
||||
<br><b>Level</b><br>{level}<br>
|
||||
<br><b>Message</b><br>{message}<br>
|
||||
<br><b>Log Name</b><br>{logname}<br><br><b>Method</b><br>{method}<br>
|
||||
<br><b>File</b><br>{fileName}<br>
|
||||
<br><b>Line</b><br>{lineNumber}<br>
|
||||
<br><b>Host</b><br>{host}<br>
|
||||
<br><b>Timestamp</b><br>{timestamp}<br>
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(LogDetailWidget, self).__init__(parent=parent)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
label = QtWidgets.QLabel("Detail")
|
||||
detail_widget = LogDetailTextEdit()
|
||||
detail_widget.setReadOnly(True)
|
||||
layout.addWidget(label)
|
||||
layout.addWidget(detail_widget)
|
||||
|
||||
self.detail_widget = detail_widget
|
||||
|
||||
self.setEnabled(True)
|
||||
|
||||
self.set_detail(None)
|
||||
|
||||
def set_detail(self, detail_data):
|
||||
if not detail_data:
|
||||
self.detail_widget.setText("")
|
||||
return
|
||||
|
||||
data = dict()
|
||||
for row in self.data_rows:
|
||||
value = detail_data.get(row) or "< Not set >"
|
||||
data[row] = value
|
||||
|
||||
|
||||
self.detail_widget.setHtml(self.html_text.format(**data))
|
||||
|
||||
|
||||
class LogDetailTextEdit(QtWidgets.QTextEdit):
|
||||
"""QTextEdit that displays version specific information.
|
||||
|
||||
This also overrides the context menu to add actions like copying
|
||||
source path to clipboard or copying the raw data of the version
|
||||
to clipboard.
|
||||
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
super(LogDetailTextEdit, self).__init__(parent=parent)
|
||||
|
||||
# self.data = {
|
||||
# "source": None,
|
||||
# "raw": None
|
||||
# }
|
||||
#
|
||||
# def contextMenuEvent(self, event):
|
||||
# """Context menu with additional actions"""
|
||||
# menu = self.createStandardContextMenu()
|
||||
#
|
||||
# # Add additional actions when any text so we can assume
|
||||
# # the version is set.
|
||||
# if self.toPlainText().strip():
|
||||
#
|
||||
# menu.addSeparator()
|
||||
# action = QtWidgets.QAction("Copy source path to clipboard",
|
||||
# menu)
|
||||
# action.triggered.connect(self.on_copy_source)
|
||||
# menu.addAction(action)
|
||||
#
|
||||
# action = QtWidgets.QAction("Copy raw data to clipboard",
|
||||
# menu)
|
||||
# action.triggered.connect(self.on_copy_raw)
|
||||
# menu.addAction(action)
|
||||
#
|
||||
# menu.exec_(event.globalPos())
|
||||
# del menu
|
||||
#
|
||||
# def on_copy_source(self):
|
||||
# """Copy formatted source path to clipboard"""
|
||||
# source = self.data.get("source", None)
|
||||
# if not source:
|
||||
# return
|
||||
#
|
||||
# # path = source.format(root=api.registered_root())
|
||||
# # clipboard = QtWidgets.QApplication.clipboard()
|
||||
# # clipboard.setText(path)
|
||||
#
|
||||
# def on_copy_raw(self):
|
||||
# """Copy raw version data to clipboard
|
||||
#
|
||||
# The data is string formatted with `pprint.pformat`.
|
||||
#
|
||||
# """
|
||||
# raw = self.data.get("raw", None)
|
||||
# if not raw:
|
||||
# return
|
||||
#
|
||||
# raw_text = pprint.pformat(raw)
|
||||
# clipboard = QtWidgets.QApplication.clipboard()
|
||||
# clipboard.setText(raw_text)
|
||||
5
pype/logging/tray/__init__.py
Normal file
5
pype/logging/tray/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from .logging_module import LoggingModule
|
||||
|
||||
|
||||
def tray_init(tray_widget, main_widget):
|
||||
return LoggingModule(main_widget, tray_widget)
|
||||
36
pype/logging/tray/logging_module.py
Normal file
36
pype/logging/tray/logging_module.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import os
|
||||
from Qt import QtWidgets
|
||||
|
||||
from pypeapp import Logger
|
||||
|
||||
from ..gui.app import LogsWindow
|
||||
|
||||
log = Logger().get_logger("LoggingModule", "logging")
|
||||
|
||||
|
||||
class LoggingModule:
|
||||
def __init__(self, main_parent=None, parent=None):
|
||||
self.parent = parent
|
||||
|
||||
self.window = LogsWindow()
|
||||
|
||||
# Definition of Tray menu
|
||||
def tray_menu(self, parent_menu):
|
||||
# Menu for Tray App
|
||||
menu = QtWidgets.QMenu('Logging', parent_menu)
|
||||
# menu.setProperty('submenu', 'on')
|
||||
|
||||
show_action = QtWidgets.QAction("Show Logs", menu)
|
||||
show_action.triggered.connect(self.on_show_logs)
|
||||
menu.addAction(show_action)
|
||||
|
||||
parent_menu.addMenu(menu)
|
||||
|
||||
def tray_start(self):
|
||||
pass
|
||||
|
||||
def process_modules(self, modules):
|
||||
return
|
||||
|
||||
def on_show_logs(self):
|
||||
self.window.show()
|
||||
|
|
@ -17,6 +17,9 @@ class IntegrateCleanComponentData(pyblish.api.InstancePlugin):
|
|||
|
||||
for comp in instance.data['representations']:
|
||||
self.log.debug('component {}'.format(comp))
|
||||
|
||||
if "%" in comp['published_path'] or "#" in comp['published_path']:
|
||||
continue
|
||||
|
||||
if comp.get('thumbnail') or ("thumbnail" in comp.get('tags', [])):
|
||||
os.remove(comp['published_path'])
|
||||
|
|
|
|||
20
pype/plugins/global/publish/collect_anatomy.py
Normal file
20
pype/plugins/global/publish/collect_anatomy.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Requires:
|
||||
None
|
||||
Provides:
|
||||
context -> anatomy (pypeapp.Anatomy)
|
||||
"""
|
||||
|
||||
from pypeapp import Anatomy
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectAnatomy(pyblish.api.ContextPlugin):
|
||||
"""Collect Anatomy into Context"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = "Collect Anatomy"
|
||||
|
||||
def process(self, context):
|
||||
context.data['anatomy'] = Anatomy()
|
||||
self.log.info("Anatomy templates collected...")
|
||||
|
|
@ -1,3 +1,10 @@
|
|||
"""
|
||||
Requires:
|
||||
None
|
||||
Provides:
|
||||
context -> comment (str)
|
||||
"""
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
"""
|
||||
Requires:
|
||||
environment -> SAPUBLISH_INPATH
|
||||
environment -> SAPUBLISH_OUTPATH
|
||||
|
||||
Provides:
|
||||
context -> returnJsonPath (str)
|
||||
context -> project
|
||||
context -> asset
|
||||
instance -> destination_list (list)
|
||||
instance -> representations (list)
|
||||
instance -> source (list)
|
||||
instance -> representations
|
||||
"""
|
||||
|
||||
import os
|
||||
import pyblish.api
|
||||
from avalon import io
|
||||
|
|
@ -31,9 +46,25 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
|||
in_data = json.load(f)
|
||||
|
||||
asset_name = in_data['asset']
|
||||
family_preset_key = in_data.get('family_preset_key', '')
|
||||
family = in_data['family']
|
||||
subset = in_data['subset']
|
||||
|
||||
# Load presets
|
||||
presets = context.data.get("presets")
|
||||
if not presets:
|
||||
from pypeapp import config
|
||||
presets = config.get_presets()
|
||||
|
||||
# Get from presets anatomy key that will be used for getting template
|
||||
# - default integrate new is used if not set
|
||||
anatomy_key = presets.get(
|
||||
"standalone_publish", {}).get(
|
||||
"families", {}).get(
|
||||
family_preset_key, {}).get(
|
||||
"anatomy_template"
|
||||
)
|
||||
|
||||
project = io.find_one({'type': 'project'})
|
||||
asset = io.find_one({
|
||||
'type': 'asset',
|
||||
|
|
@ -50,6 +81,8 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
|||
"label": subset,
|
||||
"name": subset,
|
||||
"family": family,
|
||||
"frameStart": in_data.get("representations", [None])[0].get("frameStart", None),
|
||||
"frameEnd": in_data.get("representations", [None])[0].get("frameEnd", None),
|
||||
"families": [family, 'ftrack'],
|
||||
})
|
||||
self.log.info("collected instance: {}".format(instance.data))
|
||||
|
|
@ -63,7 +96,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin):
|
|||
|
||||
component['destination'] = component['files']
|
||||
component['stagingDir'] = component['stagingDir']
|
||||
component['anatomy_template'] = 'render'
|
||||
# Do not set anatomy_template if not specified
|
||||
if anatomy_key:
|
||||
component['anatomy_template'] = anatomy_key
|
||||
if isinstance(component['files'], list):
|
||||
collections, remainder = clique.assemble(component['files'])
|
||||
self.log.debug("collecting sequence: {}".format(collections))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
"""
|
||||
Requires:
|
||||
context -> currentFile (str)
|
||||
Provides:
|
||||
context -> label (str)
|
||||
"""
|
||||
|
||||
import os
|
||||
import pyblish.api
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
"""
|
||||
Requires:
|
||||
None
|
||||
|
||||
Provides:
|
||||
context -> currentFile (str)
|
||||
"""
|
||||
|
||||
import os
|
||||
import pyblish.api
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
"""
|
||||
Requires:
|
||||
environment -> DEADLINE_PATH
|
||||
|
||||
Provides:
|
||||
context -> deadlineUser (str)
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
|
@ -54,4 +62,3 @@ class CollectDeadlineUser(pyblish.api.ContextPlugin):
|
|||
|
||||
self.log.info("Found Deadline user: {}".format(user))
|
||||
context.data['deadlineUser'] = user
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
"""
|
||||
Requires:
|
||||
environment -> PYPE_PUBLISH_PATHS
|
||||
context -> workspaceDir
|
||||
|
||||
Provides:
|
||||
context -> user (str)
|
||||
instance -> new instance
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import copy
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
"""
|
||||
Requires:
|
||||
none
|
||||
|
||||
Provides:
|
||||
context -> machine (str)
|
||||
"""
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import os
|
||||
import json
|
||||
"""
|
||||
Requires:
|
||||
config_data -> ftrack.output_representation
|
||||
|
||||
Provides:
|
||||
context -> output_repre_config (str)
|
||||
"""
|
||||
|
||||
import pyblish.api
|
||||
from pypeapp import config
|
||||
|
||||
|
|
@ -9,7 +15,7 @@ class CollectOutputRepreConfig(pyblish.api.ContextPlugin):
|
|||
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = "Collect Config for representation"
|
||||
hosts = ["shell"]
|
||||
hosts = ["shell", "standalonepublisher"]
|
||||
|
||||
def process(self, context):
|
||||
config_data = config.get_presets()["ftrack"]["output_representation"]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
"""
|
||||
Requires:
|
||||
config_data -> colorspace.default
|
||||
config_data -> dataflow.default
|
||||
|
||||
Provides:
|
||||
context -> presets
|
||||
"""
|
||||
|
||||
from pyblish import api
|
||||
from pypeapp import config
|
||||
|
||||
|
|
@ -5,7 +14,7 @@ from pypeapp import config
|
|||
class CollectPresets(api.ContextPlugin):
|
||||
"""Collect Presets."""
|
||||
|
||||
order = api.CollectorOrder
|
||||
order = api.CollectorOrder - 0.491
|
||||
label = "Collect Presets"
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
"""
|
||||
Requires:
|
||||
None
|
||||
|
||||
Provides:
|
||||
context -> projectData
|
||||
"""
|
||||
|
||||
import pyblish.api
|
||||
import pype.api as pype
|
||||
|
||||
|
||||
|
||||
class CollectProjectData(pyblish.api.ContextPlugin):
|
||||
"""Collecting project data from avalon db"""
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ class CollectSceneVersion(pyblish.api.ContextPlugin):
|
|||
label = 'Collect Version'
|
||||
|
||||
def process(self, context):
|
||||
if "standalonepublisher" in context.data.get("host", []):
|
||||
return
|
||||
|
||||
filename = os.path.basename(context.data.get('currentFile'))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,87 @@
|
|||
"""
|
||||
Requires:
|
||||
session -> AVALON_PROJECT
|
||||
context -> anatomy (pypeapp.Anatomy)
|
||||
instance -> subset
|
||||
instance -> asset
|
||||
instance -> family
|
||||
|
||||
import pype.api as pype
|
||||
from pypeapp import Anatomy
|
||||
Provides:
|
||||
instance -> template
|
||||
instance -> assumedTemplateData
|
||||
instance -> assumedDestination
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from avalon import io, api
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectTemplates(pyblish.api.ContextPlugin):
|
||||
"""Inject the current working file into context"""
|
||||
class CollectTemplates(pyblish.api.InstancePlugin):
|
||||
"""Fill templates with data needed for publish"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = "Collect Templates"
|
||||
order = pyblish.api.CollectorOrder + 0.1
|
||||
label = "Collect and fill Templates"
|
||||
hosts = ["maya", "nuke", "standalonepublisher"]
|
||||
|
||||
def process(self, context):
|
||||
context.data['anatomy'] = Anatomy()
|
||||
self.log.info("Anatomy templates collected...")
|
||||
def process(self, instance):
|
||||
# get all the stuff from the database
|
||||
subset_name = instance.data["subset"]
|
||||
asset_name = instance.data["asset"]
|
||||
project_name = api.Session["AVALON_PROJECT"]
|
||||
|
||||
project = io.find_one({"type": "project",
|
||||
"name": project_name},
|
||||
projection={"config": True, "data": True})
|
||||
|
||||
template = project["config"]["template"]["publish"]
|
||||
anatomy = instance.context.data['anatomy']
|
||||
|
||||
asset = io.find_one({"type": "asset",
|
||||
"name": asset_name,
|
||||
"parent": project["_id"]})
|
||||
|
||||
assert asset, ("No asset found by the name '{}' "
|
||||
"in project '{}'".format(asset_name, project_name))
|
||||
silo = asset['silo']
|
||||
|
||||
subset = io.find_one({"type": "subset",
|
||||
"name": subset_name,
|
||||
"parent": asset["_id"]})
|
||||
|
||||
# assume there is no version yet, we start at `1`
|
||||
version = None
|
||||
version_number = 1
|
||||
if subset is not None:
|
||||
version = io.find_one({"type": "version",
|
||||
"parent": subset["_id"]},
|
||||
sort=[("name", -1)])
|
||||
|
||||
# if there is a subset there ought to be version
|
||||
if version is not None:
|
||||
version_number += int(version["name"])
|
||||
|
||||
hierarchy = asset['data']['parents']
|
||||
if hierarchy:
|
||||
# hierarchy = os.path.sep.join(hierarchy)
|
||||
hierarchy = os.path.join(*hierarchy)
|
||||
|
||||
template_data = {"root": api.Session["AVALON_PROJECTS"],
|
||||
"project": {"name": project_name,
|
||||
"code": project['data']['code']},
|
||||
"silo": silo,
|
||||
"family": instance.data['family'],
|
||||
"asset": asset_name,
|
||||
"subset": subset_name,
|
||||
"version": version_number,
|
||||
"hierarchy": hierarchy,
|
||||
"representation": "TEMP"}
|
||||
|
||||
instance.data["template"] = template
|
||||
instance.data["assumedTemplateData"] = template_data
|
||||
|
||||
# We take the parent folder of representation 'filepath'
|
||||
instance.data["assumedDestination"] = os.path.dirname(
|
||||
(anatomy.format(template_data))["publish"]["path"]
|
||||
)
|
||||
|
|
|
|||
126
pype/plugins/global/publish/extract_thumbnail_sa.py
Normal file
126
pype/plugins/global/publish/extract_thumbnail_sa.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import os
|
||||
import tempfile
|
||||
import subprocess
|
||||
import pyblish.api
|
||||
import pype.api
|
||||
|
||||
|
||||
class ExtractThumbnail(pyblish.api.InstancePlugin):
|
||||
"""Extract jpeg thumbnail from component input from standalone publisher
|
||||
|
||||
Uses jpeg file from component if possible (when single or multiple jpegs
|
||||
are loaded to component selected as thumbnail) otherwise extracts from
|
||||
input file/s single jpeg to temp.
|
||||
"""
|
||||
|
||||
label = "Extract Thumbnail"
|
||||
hosts = ["standalonepublisher"]
|
||||
order = pyblish.api.ExtractorOrder
|
||||
|
||||
def process(self, instance):
|
||||
repres = instance.data.get('representations')
|
||||
if not repres:
|
||||
return
|
||||
|
||||
thumbnail_repre = None
|
||||
for repre in repres:
|
||||
if repre.get("thumbnail"):
|
||||
thumbnail_repre = repre
|
||||
break
|
||||
|
||||
if not thumbnail_repre:
|
||||
return
|
||||
|
||||
files = thumbnail_repre.get("files")
|
||||
if not files:
|
||||
return
|
||||
|
||||
if isinstance(files, list):
|
||||
files_len = len(files)
|
||||
file = str(files[0])
|
||||
else:
|
||||
files_len = 1
|
||||
file = files
|
||||
|
||||
is_jpeg = False
|
||||
if file.endswith(".jpeg") or file.endswith(".jpg"):
|
||||
is_jpeg = True
|
||||
|
||||
if is_jpeg and files_len == 1:
|
||||
# skip if already is single jpeg file
|
||||
return
|
||||
|
||||
elif is_jpeg:
|
||||
# use first frame as thumbnail if is sequence of jpegs
|
||||
full_thumbnail_path = file
|
||||
self.log.info(
|
||||
"For thumbnail is used file: {}".format(full_thumbnail_path)
|
||||
)
|
||||
|
||||
else:
|
||||
# Convert to jpeg if not yet
|
||||
full_input_path = os.path.join(thumbnail_repre["stagingDir"], file)
|
||||
self.log.info("input {}".format(full_input_path))
|
||||
|
||||
full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1]
|
||||
self.log.info("output {}".format(full_thumbnail_path))
|
||||
|
||||
config_data = instance.context.data.get("output_repre_config", {})
|
||||
|
||||
proj_name = os.environ.get("AVALON_PROJECT", "__default__")
|
||||
profile = config_data.get(
|
||||
proj_name,
|
||||
config_data.get("__default__", {})
|
||||
)
|
||||
|
||||
ffmpeg_path = os.getenv("FFMPEG_PATH", "")
|
||||
if ffmpeg_path:
|
||||
ffmpeg_path += "/ffmpeg"
|
||||
else:
|
||||
ffmpeg_path = "ffmpeg"
|
||||
|
||||
jpeg_items = []
|
||||
jpeg_items.append(ffmpeg_path)
|
||||
# override file if already exists
|
||||
jpeg_items.append("-y")
|
||||
# add input filters from peresets
|
||||
if profile:
|
||||
jpeg_items.extend(profile.get('input', []))
|
||||
# input file
|
||||
jpeg_items.append("-i {}".format(full_input_path))
|
||||
# extract only single file
|
||||
jpeg_items.append("-vframes 1")
|
||||
# output file
|
||||
jpeg_items.append(full_thumbnail_path)
|
||||
|
||||
subprocess_jpeg = " ".join(jpeg_items)
|
||||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprocess_jpeg))
|
||||
subprocess.Popen(
|
||||
subprocess_jpeg,
|
||||
stdout=subprocess.PIPE,
|
||||
shell=True
|
||||
)
|
||||
|
||||
# remove thumbnail key from origin repre
|
||||
thumbnail_repre.pop("thumbnail")
|
||||
|
||||
filename = os.path.basename(full_thumbnail_path)
|
||||
staging_dir = os.path.dirname(full_thumbnail_path)
|
||||
|
||||
# create new thumbnail representation
|
||||
representation = {
|
||||
'name': 'jpg',
|
||||
'ext': 'jpg',
|
||||
'files': filename,
|
||||
"stagingDir": staging_dir,
|
||||
"thumbnail": True,
|
||||
"tags": []
|
||||
}
|
||||
|
||||
# # add Delete tag when temp file was rendered
|
||||
# if not is_jpeg:
|
||||
# representation["tags"].append("delete")
|
||||
|
||||
instance.data["representations"].append(representation)
|
||||
|
|
@ -30,7 +30,8 @@ class IntegrateAssumedDestination(pyblish.api.InstancePlugin):
|
|||
"resources")
|
||||
|
||||
# Clean the path
|
||||
mock_destination = os.path.abspath(os.path.normpath(mock_destination)).replace("\\", "/")
|
||||
mock_destination = os.path.abspath(
|
||||
os.path.normpath(mock_destination)).replace("\\", "/")
|
||||
|
||||
# Define resource destination and transfers
|
||||
resources = instance.data.get("resources", list())
|
||||
|
|
@ -38,7 +39,8 @@ class IntegrateAssumedDestination(pyblish.api.InstancePlugin):
|
|||
for resource in resources:
|
||||
|
||||
# Add destination to the resource
|
||||
source_filename = os.path.basename(resource["source"]).replace("\\", "/")
|
||||
source_filename = os.path.basename(
|
||||
resource["source"]).replace("\\", "/")
|
||||
destination = os.path.join(mock_destination, source_filename)
|
||||
|
||||
# Force forward slashes to fix issue with software unable
|
||||
|
|
@ -53,7 +55,8 @@ class IntegrateAssumedDestination(pyblish.api.InstancePlugin):
|
|||
files = resource['files']
|
||||
for fsrc in files:
|
||||
fname = os.path.basename(fsrc)
|
||||
fdest = os.path.join(mock_destination, fname).replace("\\", "/")
|
||||
fdest = os.path.join(
|
||||
mock_destination, fname).replace("\\", "/")
|
||||
transfers.append([fsrc, fdest])
|
||||
|
||||
instance.data["resources"] = resources
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
if repre.get("frameStart"):
|
||||
frame_start_padding = len(str(
|
||||
repre.get("frameEnd")))
|
||||
index_frame_start = repre.get("frameStart")
|
||||
index_frame_start = int(repre.get("frameStart"))
|
||||
|
||||
dst_padding_exp = src_padding_exp
|
||||
for i in src_collection.indexes:
|
||||
|
|
@ -322,7 +322,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
dst_padding = dst_padding_exp % index_frame_start
|
||||
index_frame_start += 1
|
||||
|
||||
dst = "{0}{1}{2}".format(dst_head, dst_padding, dst_tail)
|
||||
dst = "{0}{1}{2}".format(dst_head, dst_padding, dst_tail).replace("..", ".")
|
||||
self.log.debug("destination: `{}`".format(dst))
|
||||
src = os.path.join(stagingdir, src_file_name)
|
||||
self.log.debug("source: {}".format(src))
|
||||
|
|
@ -357,7 +357,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
src = os.path.join(stagingdir, fname)
|
||||
anatomy_filled = anatomy.format(template_data)
|
||||
dst = os.path.normpath(
|
||||
anatomy_filled[template_name]["path"])
|
||||
anatomy_filled[template_name]["path"]).replace("..", ".")
|
||||
|
||||
instance.data["transfers"].append([src, dst])
|
||||
|
||||
|
|
@ -440,6 +440,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
Returns:
|
||||
None
|
||||
"""
|
||||
src = os.path.normpath(src)
|
||||
dst = os.path.normpath(dst)
|
||||
|
||||
self.log.debug("Copying file .. {} -> {}".format(src, dst))
|
||||
dirname = os.path.dirname(dst)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import json
|
||||
import re
|
||||
from pprint import pprint
|
||||
import logging
|
||||
|
||||
from avalon import api, io
|
||||
|
|
@ -147,7 +146,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
"PYPE_ROOT"
|
||||
]
|
||||
|
||||
|
||||
def _submit_deadline_post_job(self, instance, job):
|
||||
"""
|
||||
Deadline specific code separated from :meth:`process` for sake of
|
||||
|
|
@ -192,7 +190,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
# Transfer the environment from the original job to this dependent
|
||||
# job so they use the same environment
|
||||
|
||||
|
||||
environment = job["Props"].get("Env", {})
|
||||
i = 0
|
||||
for index, key in enumerate(environment):
|
||||
|
|
@ -295,7 +292,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
# Optional metadata (for debugging)
|
||||
"metadata": {
|
||||
"instance": data,
|
||||
"job": job,
|
||||
"job": render_job,
|
||||
"session": api.Session.copy()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,4 @@ class CollectActiveViewer(pyblish.api.ContextPlugin):
|
|||
hosts = ["nuke"]
|
||||
|
||||
def process(self, context):
|
||||
context.data["ViewerProcess"] = nuke.ViewerProcess.node()
|
||||
context.data["ActiveViewer"] = nuke.activeViewer()
|
||||
|
|
@ -16,3 +16,9 @@ class ValidateActiveViewer(pyblish.api.ContextPlugin):
|
|||
assert viewer_process_node, (
|
||||
"Missing active viewer process! Please click on output write node and push key number 1-9"
|
||||
)
|
||||
active_viewer = context.data["ActiveViewer"]
|
||||
active_input = active_viewer.activeInput()
|
||||
|
||||
assert active_input is not None, (
|
||||
"Missing active viewer input! Please click on output write node and push key number 1-9"
|
||||
)
|
||||
|
|
@ -14,6 +14,7 @@ class LoadLuts(api.Loader):
|
|||
order = 0
|
||||
icon = "cc"
|
||||
color = style.colors.light
|
||||
ignore_attr = ["useLifetime"]
|
||||
|
||||
def load(self, context, name, namespace, data):
|
||||
"""
|
||||
|
|
@ -83,6 +84,8 @@ class LoadLuts(api.Loader):
|
|||
for ef_name, ef_val in nodes_order.items():
|
||||
node = nuke.createNode(ef_val["class"])
|
||||
for k, v in ef_val["node"].items():
|
||||
if k in self.ignore_attr:
|
||||
continue
|
||||
if isinstance(v, list) and len(v) > 4:
|
||||
node[k].setAnimated()
|
||||
for i, value in enumerate(v):
|
||||
|
|
@ -194,6 +197,8 @@ class LoadLuts(api.Loader):
|
|||
for ef_name, ef_val in nodes_order.items():
|
||||
node = nuke.createNode(ef_val["class"])
|
||||
for k, v in ef_val["node"].items():
|
||||
if k in self.ignore_attr:
|
||||
continue
|
||||
if isinstance(v, list) and len(v) > 3:
|
||||
node[k].setAnimated()
|
||||
for i, value in enumerate(v):
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class LoadLutsInputProcess(api.Loader):
|
|||
order = 0
|
||||
icon = "eye"
|
||||
color = style.colors.alert
|
||||
ignore_attr = ["useLifetime"]
|
||||
|
||||
def load(self, context, name, namespace, data):
|
||||
"""
|
||||
|
|
@ -83,6 +84,8 @@ class LoadLutsInputProcess(api.Loader):
|
|||
for ef_name, ef_val in nodes_order.items():
|
||||
node = nuke.createNode(ef_val["class"])
|
||||
for k, v in ef_val["node"].items():
|
||||
if k in self.ignore_attr:
|
||||
continue
|
||||
if isinstance(v, list) and len(v) > 4:
|
||||
node[k].setAnimated()
|
||||
for i, value in enumerate(v):
|
||||
|
|
@ -196,6 +199,8 @@ class LoadLutsInputProcess(api.Loader):
|
|||
for ef_name, ef_val in nodes_order.items():
|
||||
node = nuke.createNode(ef_val["class"])
|
||||
for k, v in ef_val["node"].items():
|
||||
if k in self.ignore_attr:
|
||||
continue
|
||||
if isinstance(v, list) and len(v) > 3:
|
||||
node[k].setAnimated()
|
||||
for i, value in enumerate(v):
|
||||
|
|
|
|||
|
|
@ -15,21 +15,17 @@ class CreateOutputNode(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
# capture selection state
|
||||
with maintained_selection():
|
||||
# deselect all allNodes
|
||||
self.log.info(context.data["ActiveViewer"])
|
||||
active_node = [node for inst in context[:]
|
||||
for node in inst[:]
|
||||
if "ak:family" in node.knobs()]
|
||||
|
||||
active_viewer = context.data["ActiveViewer"]
|
||||
active_input = active_viewer.activeInput()
|
||||
active_node = active_viewer.node()
|
||||
|
||||
|
||||
last_viewer_node = active_node.input(active_input)
|
||||
|
||||
name = last_viewer_node.name()
|
||||
self.log.info("Node name: {}".format(name))
|
||||
if active_node:
|
||||
self.log.info(active_node)
|
||||
active_node = active_node[0]
|
||||
self.log.info(active_node)
|
||||
active_node['selected'].setValue(True)
|
||||
|
||||
# select only instance render node
|
||||
last_viewer_node['selected'].setValue(True)
|
||||
output_node = nuke.createNode("Output")
|
||||
|
||||
# deselect all and select the original selection
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import nuke
|
|||
import pyblish.api
|
||||
import pype
|
||||
|
||||
|
||||
class ExtractReviewData(pype.api.Extractor):
|
||||
"""Extracts movie and thumbnail with baked in luts
|
||||
|
||||
|
|
@ -48,9 +47,9 @@ class ExtractReviewData(pype.api.Extractor):
|
|||
|
||||
assert instance.data['representations'][0]['files'], "Instance data files should't be empty!"
|
||||
|
||||
import nuke
|
||||
temporary_nodes = []
|
||||
stagingDir = instance.data['representations'][0]["stagingDir"].replace("\\", "/")
|
||||
stagingDir = instance.data[
|
||||
'representations'][0]["stagingDir"].replace("\\", "/")
|
||||
self.log.debug("StagingDir `{0}`...".format(stagingDir))
|
||||
|
||||
collection = instance.data.get("collection", None)
|
||||
|
|
@ -70,16 +69,24 @@ class ExtractReviewData(pype.api.Extractor):
|
|||
first_frame = instance.data.get("frameStart", None)
|
||||
last_frame = instance.data.get("frameEnd", None)
|
||||
|
||||
node = previous_node = nuke.createNode("Read")
|
||||
rnode = nuke.createNode("Read")
|
||||
|
||||
node["file"].setValue(
|
||||
rnode["file"].setValue(
|
||||
os.path.join(stagingDir, fname).replace("\\", "/"))
|
||||
|
||||
node["first"].setValue(first_frame)
|
||||
node["origfirst"].setValue(first_frame)
|
||||
node["last"].setValue(last_frame)
|
||||
node["origlast"].setValue(last_frame)
|
||||
temporary_nodes.append(node)
|
||||
rnode["first"].setValue(first_frame)
|
||||
rnode["origfirst"].setValue(first_frame)
|
||||
rnode["last"].setValue(last_frame)
|
||||
rnode["origlast"].setValue(last_frame)
|
||||
temporary_nodes.append(rnode)
|
||||
previous_node = rnode
|
||||
|
||||
# get input process and connect it to baking
|
||||
ipn = self.get_view_process_node()
|
||||
if ipn is not None:
|
||||
ipn.setInput(0, previous_node)
|
||||
previous_node = ipn
|
||||
temporary_nodes.append(ipn)
|
||||
|
||||
reformat_node = nuke.createNode("Reformat")
|
||||
|
||||
|
|
@ -95,22 +102,10 @@ class ExtractReviewData(pype.api.Extractor):
|
|||
previous_node = reformat_node
|
||||
temporary_nodes.append(reformat_node)
|
||||
|
||||
viewer_process_node = instance.context.data.get("ViewerProcess")
|
||||
dag_node = None
|
||||
if viewer_process_node:
|
||||
dag_node = nuke.createNode(viewer_process_node.Class())
|
||||
dag_node.setInput(0, previous_node)
|
||||
previous_node = dag_node
|
||||
temporary_nodes.append(dag_node)
|
||||
# Copy viewer process values
|
||||
excludedKnobs = ["name", "xpos", "ypos"]
|
||||
for item in viewer_process_node.knobs().keys():
|
||||
if item not in excludedKnobs and item in dag_node.knobs():
|
||||
x1 = viewer_process_node[item]
|
||||
x2 = dag_node[item]
|
||||
x2.fromScript(x1.toScript(False))
|
||||
else:
|
||||
self.log.warning("No viewer node found.")
|
||||
dag_node = nuke.createNode("OCIODisplay")
|
||||
dag_node.setInput(0, previous_node)
|
||||
previous_node = dag_node
|
||||
temporary_nodes.append(dag_node)
|
||||
|
||||
# create write node
|
||||
write_node = nuke.createNode("Write")
|
||||
|
|
@ -164,3 +159,29 @@ class ExtractReviewData(pype.api.Extractor):
|
|||
# Clean up
|
||||
for node in temporary_nodes:
|
||||
nuke.delete(node)
|
||||
|
||||
def get_view_process_node(self):
|
||||
|
||||
# Select only the target node
|
||||
if nuke.selectedNodes():
|
||||
[n.setSelected(False) for n in nuke.selectedNodes()]
|
||||
|
||||
ipn_orig = None
|
||||
for v in [n for n in nuke.allNodes()
|
||||
if "Viewer" in n.Class()]:
|
||||
ip = v['input_process'].getValue()
|
||||
ipn = v['input_process_node'].getValue()
|
||||
if "VIEWER_INPUT" not in ipn and ip:
|
||||
ipn_orig = nuke.toNode(ipn)
|
||||
ipn_orig.setSelected(True)
|
||||
|
||||
if ipn_orig:
|
||||
nuke.nodeCopy('%clipboard%')
|
||||
|
||||
[n.setSelected(False) for n in nuke.selectedNodes()] # Deselect all
|
||||
|
||||
nuke.nodePaste('%clipboard%')
|
||||
|
||||
ipn = nuke.selectedNode()
|
||||
|
||||
return ipn
|
||||
|
|
|
|||
|
|
@ -81,3 +81,5 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin):
|
|||
).format(__name__)
|
||||
|
||||
instance.data['collection'] = collection
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ class ValidateScript(pyblish.api.InstancePlugin):
|
|||
# Set frame range with handles
|
||||
# asset_attributes["frameStart"] -= handle_start
|
||||
# asset_attributes["frameEnd"] += handle_end
|
||||
if len(str(asset_attributes["fps"])) > 4:
|
||||
asset_attributes["fps"] = float("{0:.8f}".format(asset_attributes["fps"]))
|
||||
|
||||
# Get values from nukescript
|
||||
script_attributes = {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ def avalon_api_publish(data, gui=True):
|
|||
"-pp", os.pathsep.join(pyblish.api.registered_paths())
|
||||
]
|
||||
|
||||
os.environ["PYBLISH_HOSTS"] = "shell"
|
||||
os.environ["PYBLISH_HOSTS"] = "standalonepublisher"
|
||||
os.environ["SAPUBLISH_INPATH"] = json_data_path
|
||||
|
||||
if gui:
|
||||
|
|
@ -139,7 +139,7 @@ def cli_publish(data, gui=True):
|
|||
if gui:
|
||||
args += ["gui"]
|
||||
|
||||
os.environ["PYBLISH_HOSTS"] = "shell"
|
||||
os.environ["PYBLISH_HOSTS"] = "standalonepublisher"
|
||||
os.environ["SAPUBLISH_INPATH"] = json_data_path
|
||||
os.environ["SAPUBLISH_OUTPATH"] = return_data_path
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ HelpRole = QtCore.Qt.UserRole + 2
|
|||
FamilyRole = QtCore.Qt.UserRole + 3
|
||||
ExistsRole = QtCore.Qt.UserRole + 4
|
||||
PluginRole = QtCore.Qt.UserRole + 5
|
||||
PluginKeyRole = QtCore.Qt.UserRole + 6
|
||||
|
||||
from ..resources import get_resource
|
||||
from .button_from_svgs import SvgResizable, SvgButton
|
||||
|
|
|
|||
|
|
@ -220,15 +220,21 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
self._process_data(data)
|
||||
|
||||
def load_data_with_probe(self, filepath):
|
||||
ffprobe_path = os.getenv("FFMPEG_PATH", "")
|
||||
if ffprobe_path:
|
||||
ffprobe_path += '/ffprobe'
|
||||
else:
|
||||
ffprobe_path = 'ffprobe'
|
||||
|
||||
args = [
|
||||
'ffprobe',
|
||||
ffprobe_path,
|
||||
'-v', 'quiet',
|
||||
'-print_format', 'json',
|
||||
'-show_format',
|
||||
'-show_streams', filepath
|
||||
]
|
||||
ffprobe_p = subprocess.Popen(
|
||||
args,
|
||||
' '.join(args),
|
||||
stdout=subprocess.PIPE,
|
||||
shell=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import json
|
|||
from collections import namedtuple
|
||||
|
||||
from . import QtWidgets, QtCore
|
||||
from . import HelpRole, FamilyRole, ExistsRole, PluginRole
|
||||
from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole
|
||||
from . import FamilyDescriptionWidget
|
||||
|
||||
from pypeapp import config
|
||||
|
|
@ -116,8 +116,10 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
|
||||
def collect_data(self):
|
||||
plugin = self.list_families.currentItem().data(PluginRole)
|
||||
key = self.list_families.currentItem().data(PluginKeyRole)
|
||||
family = plugin.family.rsplit(".", 1)[-1]
|
||||
data = {
|
||||
'family_preset_key': key,
|
||||
'family': family,
|
||||
'subset': self.input_result.text(),
|
||||
'version': self.version_spinbox.value()
|
||||
|
|
@ -318,7 +320,7 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
has_families = False
|
||||
presets = config.get_presets().get('standalone_publish', {})
|
||||
|
||||
for creator in presets.get('families', {}).values():
|
||||
for key, creator in presets.get('families', {}).items():
|
||||
creator = namedtuple("Creator", creator.keys())(*creator.values())
|
||||
|
||||
label = creator.label or creator.family
|
||||
|
|
@ -327,6 +329,7 @@ class FamilyWidget(QtWidgets.QWidget):
|
|||
item.setData(HelpRole, creator.help or "")
|
||||
item.setData(FamilyRole, creator.family)
|
||||
item.setData(PluginRole, creator)
|
||||
item.setData(PluginKeyRole, key)
|
||||
item.setData(ExistsRole, False)
|
||||
self.list_families.addItem(item)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue