merged origin/develop
7
.gitignore
vendored
|
|
@ -91,4 +91,9 @@ website/i18n/*
|
|||
|
||||
website/debug.log
|
||||
|
||||
website/.docusaurus
|
||||
website/.docusaurus
|
||||
|
||||
# Poetry
|
||||
########
|
||||
|
||||
.poetry/
|
||||
56
CHANGELOG.md
|
|
@ -1,5 +1,61 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
## [2.17.1](https://github.com/pypeclub/openpype/tree/2.17.1) (2021-04-30)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/openpype/compare/2.17.0...2.17.1)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
- TVPaint frame range definition [\#1424](https://github.com/pypeclub/OpenPype/pull/1424)
|
||||
- PS - group all published instances [\#1415](https://github.com/pypeclub/OpenPype/pull/1415)
|
||||
- Nuke: deadline submission with gpu [\#1414](https://github.com/pypeclub/OpenPype/pull/1414)
|
||||
- Add task name to context pop up. [\#1383](https://github.com/pypeclub/OpenPype/pull/1383)
|
||||
- AE add duration validation [\#1363](https://github.com/pypeclub/OpenPype/pull/1363)
|
||||
- Maya: Support for Redshift proxies [\#1360](https://github.com/pypeclub/OpenPype/pull/1360)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Nuke: fixing undo for loaded mov and sequence [\#1433](https://github.com/pypeclub/OpenPype/pull/1433)
|
||||
- AE - validation for duration was 1 frame shorter [\#1426](https://github.com/pypeclub/OpenPype/pull/1426)
|
||||
- Houdini menu filename [\#1417](https://github.com/pypeclub/OpenPype/pull/1417)
|
||||
- Maya: Vray - problem getting all file nodes for look publishing [\#1399](https://github.com/pypeclub/OpenPype/pull/1399)
|
||||
|
||||
|
||||
## [2.17.0](https://github.com/pypeclub/openpype/tree/2.17.0) (2021-04-20)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/openpype/compare/3.0.0-beta2...2.17.0)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
- Forward compatible ftrack group [\#1243](https://github.com/pypeclub/OpenPype/pull/1243)
|
||||
- Maya: Make tx option configurable with presets [\#1328](https://github.com/pypeclub/OpenPype/pull/1328)
|
||||
- TVPaint asset name validation [\#1302](https://github.com/pypeclub/OpenPype/pull/1302)
|
||||
- TV Paint: Set initial project settings. [\#1299](https://github.com/pypeclub/OpenPype/pull/1299)
|
||||
- TV Paint: Validate mark in and out. [\#1298](https://github.com/pypeclub/OpenPype/pull/1298)
|
||||
- Validate project settings [\#1297](https://github.com/pypeclub/OpenPype/pull/1297)
|
||||
- After Effects: added SubsetManager [\#1234](https://github.com/pypeclub/OpenPype/pull/1234)
|
||||
- Show error message in pyblish UI [\#1206](https://github.com/pypeclub/OpenPype/pull/1206)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Hiero: fixing source frame from correct object [\#1362](https://github.com/pypeclub/OpenPype/pull/1362)
|
||||
- Nuke: fix colourspace, prerenders and nuke panes opening [\#1308](https://github.com/pypeclub/OpenPype/pull/1308)
|
||||
- AE remove orphaned instance from workfile - fix self.stub [\#1282](https://github.com/pypeclub/OpenPype/pull/1282)
|
||||
- Nuke: deadline submission with search replaced env values from preset [\#1194](https://github.com/pypeclub/OpenPype/pull/1194)
|
||||
- Ftrack custom attributes in bulks [\#1312](https://github.com/pypeclub/OpenPype/pull/1312)
|
||||
- Ftrack optional pypclub role [\#1303](https://github.com/pypeclub/OpenPype/pull/1303)
|
||||
- After Effects: remove orphaned instances [\#1275](https://github.com/pypeclub/OpenPype/pull/1275)
|
||||
- Avalon schema names [\#1242](https://github.com/pypeclub/OpenPype/pull/1242)
|
||||
- Handle duplication of Task name [\#1226](https://github.com/pypeclub/OpenPype/pull/1226)
|
||||
- Modified path of plugin loads for Harmony and TVPaint [\#1217](https://github.com/pypeclub/OpenPype/pull/1217)
|
||||
- Regex checks in profiles filtering [\#1214](https://github.com/pypeclub/OpenPype/pull/1214)
|
||||
- Bulk mov strict task [\#1204](https://github.com/pypeclub/OpenPype/pull/1204)
|
||||
- Update custom ftrack session attributes [\#1202](https://github.com/pypeclub/OpenPype/pull/1202)
|
||||
- Nuke: write node colorspace ignore `default\(\)` label [\#1199](https://github.com/pypeclub/OpenPype/pull/1199)
|
||||
- Nuke: reverse search to make it more versatile [\#1178](https://github.com/pypeclub/OpenPype/pull/1178)
|
||||
|
||||
|
||||
## [2.16.1](https://github.com/pypeclub/pype/tree/2.16.1) (2021-04-13)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/pype/compare/2.16.0...2.16.1)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Definition of Igniter version."""
|
||||
|
||||
__version__ = "1.0.0-beta"
|
||||
__version__ = "1.0.0-rc1"
|
||||
|
|
|
|||
|
|
@ -2,14 +2,9 @@ import pyblish.api
|
|||
|
||||
|
||||
class CollectRemoveMarked(pyblish.api.ContextPlugin):
|
||||
"""Collect model data
|
||||
"""Remove marked data
|
||||
|
||||
Ensures always only a single frame is extracted (current frame).
|
||||
|
||||
Note:
|
||||
This is a workaround so that the `pype.model` family can use the
|
||||
same pointcache extractor implementation as animation and pointcaches.
|
||||
This always enforces the "current" frame to be published.
|
||||
Remove instances that have 'remove' in their instance.data
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
"""
|
||||
Optional:
|
||||
instance.data["remove"] -> mareker for removing
|
||||
"""
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectClearInstances(pyblish.api.InstancePlugin):
|
||||
"""Clear all marked instances"""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.4999
|
||||
label = "Clear Instances"
|
||||
hosts = ["standalonepublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
self.log.debug(
|
||||
f"Instance: `{instance}` | "
|
||||
f"families: `{instance.data['families']}`")
|
||||
if instance.data.get("remove"):
|
||||
self.log.info(f"Removing: {instance}")
|
||||
instance.context.remove(instance)
|
||||
|
|
@ -127,10 +127,28 @@ class FtrackModule(
|
|||
self, old_value, new_value, changes, new_value_metadata
|
||||
):
|
||||
"""Implementation of ISettingsChangeListener interface."""
|
||||
if not self.ftrack_url:
|
||||
raise SaveWarningExc((
|
||||
"Ftrack URL is not set."
|
||||
" Can't propagate changes to Ftrack server."
|
||||
))
|
||||
|
||||
ftrack_changes = changes.get("modules", {}).get("ftrack", {})
|
||||
url_change_msg = None
|
||||
if "ftrack_server" in ftrack_changes:
|
||||
url_change_msg = (
|
||||
"Ftrack URL was changed."
|
||||
" This change may need to restart OpenPype to take affect."
|
||||
)
|
||||
|
||||
try:
|
||||
session = self.create_ftrack_session()
|
||||
except Exception:
|
||||
self.log.warning("Couldn't create ftrack session.", exc_info=True)
|
||||
|
||||
if url_change_msg:
|
||||
raise SaveWarningExc(url_change_msg)
|
||||
|
||||
raise SaveWarningExc((
|
||||
"Saving of attributes to ftrack wasn't successful,"
|
||||
" try running Create/Update Avalon Attributes in ftrack."
|
||||
|
|
@ -204,6 +222,9 @@ class FtrackModule(
|
|||
" Try running Create/Update Avalon Attributes in ftrack."
|
||||
).format(", ".join(missing_attributes)))
|
||||
|
||||
if url_change_msg:
|
||||
raise SaveWarningExc(url_change_msg)
|
||||
|
||||
def on_project_settings_save(self, *_args, **_kwargs):
|
||||
"""Implementation of ISettingsChangeListener interface."""
|
||||
# Ignore
|
||||
|
|
|
|||
|
|
@ -2,14 +2,9 @@ import pyblish.api
|
|||
|
||||
|
||||
class CollectFtrackFamilies(pyblish.api.InstancePlugin):
|
||||
"""Collect model data
|
||||
|
||||
Ensures always only a single frame is extracted (current frame).
|
||||
|
||||
Note:
|
||||
This is a workaround so that the `pype.model` family can use the
|
||||
same pointcache extractor implementation as animation and pointcaches.
|
||||
This always enforces the "current" frame to be published.
|
||||
"""Collect family for ftrack publishing
|
||||
|
||||
Add ftrack family to those instance that should be published to ftrack
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -23,6 +18,7 @@ class CollectFtrackFamilies(pyblish.api.InstancePlugin):
|
|||
"rig",
|
||||
"camera"
|
||||
]
|
||||
hosts = ["maya"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
|
|
@ -80,16 +80,20 @@ class SettingsAction(PypeModule, ITrayAction):
|
|||
|
||||
# Store if was visible
|
||||
was_visible = self.settings_window.isVisible()
|
||||
was_minimized = self.settings_window.isMinimized()
|
||||
|
||||
# Show settings gui
|
||||
self.settings_window.show()
|
||||
|
||||
if was_minimized:
|
||||
self.settings_window.showNormal()
|
||||
|
||||
# Pull window to the front.
|
||||
self.settings_window.raise_()
|
||||
self.settings_window.activateWindow()
|
||||
|
||||
# Reset content if was not visible
|
||||
if not was_visible:
|
||||
if not was_visible and not was_minimized:
|
||||
self.settings_window.reset()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
DEFAULT_SITE = 'studio'
|
||||
LOCAL_SITE = 'local'
|
||||
LOG_PROGRESS_SEC = 5 # how often log progress to DB
|
||||
DEFAULT_PRIORITY = 50 # higher is better, allowed range 1 - 1000
|
||||
|
||||
name = "sync_server"
|
||||
label = "Sync Queue"
|
||||
|
|
@ -472,6 +473,7 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
|
||||
try:
|
||||
self.sync_server_thread = SyncServerThread(self)
|
||||
|
||||
from .tray.app import SyncServerWindow
|
||||
self.widget = SyncServerWindow(self)
|
||||
except ValueError:
|
||||
|
|
@ -662,7 +664,7 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
self.connection.Session["AVALON_PROJECT"] = collection
|
||||
# retry_cnt - number of attempts to sync specific file before giving up
|
||||
retries_arr = self._get_retries_arr(collection)
|
||||
query = {
|
||||
match = {
|
||||
"type": "representation",
|
||||
"$or": [
|
||||
{"$and": [
|
||||
|
|
@ -700,10 +702,47 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
]}
|
||||
]
|
||||
}
|
||||
|
||||
aggr = [
|
||||
{"$match": match},
|
||||
{'$unwind': '$files'},
|
||||
{'$addFields': {
|
||||
'order_remote': {
|
||||
'$filter': {'input': '$files.sites', 'as': 'p',
|
||||
'cond': {'$eq': ['$$p.name', remote_site]}
|
||||
}},
|
||||
'order_local': {
|
||||
'$filter': {'input': '$files.sites', 'as': 'p',
|
||||
'cond': {'$eq': ['$$p.name', active_site]}
|
||||
}},
|
||||
}},
|
||||
{'$addFields': {
|
||||
'priority': {
|
||||
'$cond': [
|
||||
{'$size': '$order_local.priority'},
|
||||
{'$first': '$order_local.priority'},
|
||||
{'$cond': [
|
||||
{'$size': '$order_remote.priority'},
|
||||
{'$first': '$order_remote.priority'},
|
||||
self.DEFAULT_PRIORITY]}
|
||||
]
|
||||
},
|
||||
}},
|
||||
{'$group': {
|
||||
'_id': '$_id',
|
||||
# pass through context - same for representation
|
||||
'context': {'$addToSet': '$context'},
|
||||
'data': {'$addToSet': '$data'},
|
||||
# pass through files as a list
|
||||
'files': {'$addToSet': '$files'},
|
||||
'priority': {'$max': "$priority"},
|
||||
}},
|
||||
{"$sort": {'priority': -1, '_id': 1}},
|
||||
]
|
||||
log.debug("active_site:{} - remote_site:{}".format(active_site,
|
||||
remote_site))
|
||||
log.debug("query: {}".format(query))
|
||||
representations = self.connection.find(query)
|
||||
log.debug("query: {}".format(aggr))
|
||||
representations = self.connection.aggregate(aggr)
|
||||
|
||||
return representations
|
||||
|
||||
|
|
@ -749,7 +788,7 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
return SyncStatus.DO_NOTHING
|
||||
|
||||
def update_db(self, collection, new_file_id, file, representation,
|
||||
site, error=None, progress=None):
|
||||
site, error=None, progress=None, priority=None):
|
||||
"""
|
||||
Update 'provider' portion of records in DB with success (file_id)
|
||||
or error (exception)
|
||||
|
|
@ -763,12 +802,16 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
site (string): label ('gdrive', 'S3')
|
||||
error (string): exception message
|
||||
progress (float): 0-1 of progress of upload/download
|
||||
priority (int): 0-100 set priority
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
representation_id = representation.get("_id")
|
||||
file_id = file.get("_id")
|
||||
file_id = None
|
||||
if file:
|
||||
file_id = file.get("_id")
|
||||
|
||||
query = {
|
||||
"_id": representation_id
|
||||
}
|
||||
|
|
@ -780,6 +823,8 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
update["$unset"] = self._get_error_dict("", "", "")
|
||||
elif progress is not None:
|
||||
update["$set"] = self._get_progress_dict(progress)
|
||||
elif priority is not None:
|
||||
update["$set"] = self._get_priority_dict(priority, file_id)
|
||||
else:
|
||||
tries = self._get_tries_count(file, site)
|
||||
tries += 1
|
||||
|
|
@ -787,9 +832,10 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
update["$set"] = self._get_error_dict(error, tries)
|
||||
|
||||
arr_filter = [
|
||||
{'s.name': site},
|
||||
{'f._id': ObjectId(file_id)}
|
||||
{'s.name': site}
|
||||
]
|
||||
if file_id:
|
||||
arr_filter.append({'f._id': ObjectId(file_id)})
|
||||
|
||||
self.connection.database[collection].update_one(
|
||||
query,
|
||||
|
|
@ -798,7 +844,7 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
array_filters=arr_filter
|
||||
)
|
||||
|
||||
if progress is not None:
|
||||
if progress is not None or priority is not None:
|
||||
return
|
||||
|
||||
status = 'failed'
|
||||
|
|
@ -1192,6 +1238,21 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
val = {"files.$[f].sites.$[s].progress": progress}
|
||||
return val
|
||||
|
||||
def _get_priority_dict(self, priority, file_id):
|
||||
"""
|
||||
Provide priority metadata to be stored in Db.
|
||||
Used during upload/download for GUI to show.
|
||||
Args:
|
||||
priority: (int) - priority for file(s)
|
||||
Returns:
|
||||
(dictionary)
|
||||
"""
|
||||
if file_id:
|
||||
str_key = "files.$[f].sites.$[s].priority"
|
||||
else:
|
||||
str_key = "files.$[].sites.$[s].priority"
|
||||
return {str_key: int(priority)}
|
||||
|
||||
def _get_retries_arr(self, project_name):
|
||||
"""
|
||||
Returns array with allowed values in 'tries' field. If repre
|
||||
|
|
|
|||
|
|
@ -85,8 +85,26 @@ class SyncServerWindow(QtWidgets.QDialog):
|
|||
self.projects.current_project))
|
||||
|
||||
self.pause_btn.clicked.connect(self._pause)
|
||||
self.pause_btn.setAutoDefault(False)
|
||||
self.pause_btn.setDefault(False)
|
||||
repres.message_generated.connect(self._update_message)
|
||||
|
||||
self.representationWidget = repres
|
||||
|
||||
def showEvent(self, event):
|
||||
self.representationWidget.model.set_project(
|
||||
self.projects.current_project)
|
||||
self._set_running(True)
|
||||
super().showEvent(event)
|
||||
|
||||
def closeEvent(self, event):
|
||||
self._set_running(False)
|
||||
super().closeEvent(event)
|
||||
|
||||
def _set_running(self, running):
|
||||
self.representationWidget.model.is_running = running
|
||||
self.representationWidget.model.timer.setInterval(0)
|
||||
|
||||
def _pause(self):
|
||||
if self.sync_server.is_paused():
|
||||
self.sync_server.unpause_server()
|
||||
|
|
|
|||
116
openpype/modules/sync_server/tray/delegates.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import os
|
||||
from Qt import QtCore, QtWidgets, QtGui
|
||||
|
||||
from openpype.lib import PypeLogger
|
||||
from openpype.modules.sync_server.tray import lib
|
||||
|
||||
log = PypeLogger().get_logger("SyncServer")
|
||||
|
||||
|
||||
class PriorityDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Creates editable line edit to set priority on representation"""
|
||||
def paint(self, painter, option, index):
|
||||
super(PriorityDelegate, self).paint(painter, option, index)
|
||||
|
||||
if option.widget.selectionModel().isSelected(index) or \
|
||||
option.state & QtWidgets.QStyle.State_MouseOver:
|
||||
edit_icon = index.data(lib.EditIconRole)
|
||||
if not edit_icon:
|
||||
return
|
||||
|
||||
state = QtGui.QIcon.On
|
||||
mode = QtGui.QIcon.Selected
|
||||
|
||||
icon_side = 16
|
||||
icon_rect = QtCore.QRect(
|
||||
option.rect.left() + option.rect.width() - icon_side - 4,
|
||||
option.rect.top() + ((option.rect.height() - icon_side) / 2),
|
||||
icon_side,
|
||||
icon_side
|
||||
)
|
||||
|
||||
edit_icon.paint(
|
||||
painter, icon_rect,
|
||||
QtCore.Qt.AlignRight, mode, state
|
||||
)
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = PriorityLineEdit(
|
||||
parent,
|
||||
option.widget.selectionModel().selectedRows())
|
||||
editor.setFocus(True)
|
||||
return editor
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
for index in editor.selected_idxs:
|
||||
try:
|
||||
val = int(editor.text())
|
||||
except ValueError:
|
||||
val = model.sync_server.DEFAULT_PRIORITY
|
||||
model.set_priority_data(index, val)
|
||||
|
||||
|
||||
class PriorityLineEdit(QtWidgets.QLineEdit):
|
||||
"""Special LineEdit to consume Enter and store selected indexes"""
|
||||
def __init__(self, parent=None, selected_idxs=None):
|
||||
self.selected_idxs = selected_idxs
|
||||
super(PriorityLineEdit, self).__init__(parent)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
result = super(PriorityLineEdit, self).keyPressEvent(event)
|
||||
if (
|
||||
event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter)
|
||||
):
|
||||
return event.accept()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class ImageDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""
|
||||
Prints icon of site and progress of synchronization
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ImageDelegate, self).__init__(parent)
|
||||
self.icons = {}
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
super(ImageDelegate, self).paint(painter, option, index)
|
||||
option = QtWidgets.QStyleOptionViewItem(option)
|
||||
option.showDecorationSelected = True
|
||||
|
||||
provider = index.data(lib.ProviderRole)
|
||||
value = index.data(lib.ProgressRole)
|
||||
date_value = index.data(lib.DateRole)
|
||||
is_failed = index.data(lib.FailedRole)
|
||||
|
||||
if not self.icons.get(provider):
|
||||
resource_path = os.path.dirname(__file__)
|
||||
resource_path = os.path.join(resource_path, "..",
|
||||
"providers", "resources")
|
||||
pix_url = "{}/{}.png".format(resource_path, provider)
|
||||
pixmap = QtGui.QPixmap(pix_url)
|
||||
self.icons[provider] = pixmap
|
||||
else:
|
||||
pixmap = self.icons[provider]
|
||||
|
||||
padding = 10
|
||||
point = QtCore.QPoint(option.rect.x() + padding,
|
||||
option.rect.y() +
|
||||
(option.rect.height() - pixmap.height()) / 2)
|
||||
painter.drawPixmap(point, pixmap)
|
||||
|
||||
overlay_rect = option.rect.translated(0, 0)
|
||||
overlay_rect.setHeight(overlay_rect.height() * (1.0 - float(value)))
|
||||
painter.fillRect(overlay_rect,
|
||||
QtGui.QBrush(QtGui.QColor(0, 0, 0, 100)))
|
||||
text_rect = option.rect.translated(10, 0)
|
||||
painter.drawText(text_rect,
|
||||
QtCore.Qt.AlignCenter,
|
||||
date_value)
|
||||
|
||||
if is_failed:
|
||||
overlay_rect = option.rect.translated(0, 0)
|
||||
painter.fillRect(overlay_rect,
|
||||
QtGui.QBrush(QtGui.QColor(255, 0, 0, 35)))
|
||||
|
|
@ -25,6 +25,7 @@ DateRole = QtCore.Qt.UserRole + 6
|
|||
FailedRole = QtCore.Qt.UserRole + 8
|
||||
HeaderNameRole = QtCore.Qt.UserRole + 10
|
||||
FullItemRole = QtCore.Qt.UserRole + 12
|
||||
EditIconRole = QtCore.Qt.UserRole + 14
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ from Qt import QtCore
|
|||
from Qt.QtCore import Qt
|
||||
|
||||
from avalon.tools.delegates import pretty_timestamp
|
||||
from avalon.vendor import qtawesome
|
||||
|
||||
from openpype.lib import PypeLogger
|
||||
from openpype.api import get_local_site_id
|
||||
|
||||
from openpype.modules.sync_server.tray import lib
|
||||
|
||||
|
|
@ -41,6 +43,9 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
PAGE_SIZE = 20 # default page size to query for
|
||||
REFRESH_SEC = 5000 # in seconds, requery DB for new status
|
||||
|
||||
refresh_started = QtCore.Signal()
|
||||
refresh_finished = QtCore.Signal()
|
||||
|
||||
@property
|
||||
def dbcon(self):
|
||||
"""
|
||||
|
|
@ -60,6 +65,14 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
def column_filtering(self):
|
||||
return self._column_filtering
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
return self._is_running
|
||||
|
||||
@is_running.setter
|
||||
def is_running(self, state):
|
||||
self._is_running = state
|
||||
|
||||
def rowCount(self, _index):
|
||||
return len(self._data)
|
||||
|
||||
|
|
@ -78,7 +91,20 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
if orientation == Qt.Horizontal:
|
||||
return self.COLUMN_LABELS[section][0] # return name
|
||||
|
||||
@property
|
||||
def can_edit(self):
|
||||
"""Returns true if some site is user local site, eg. could edit"""
|
||||
return get_local_site_id() in (self.active_site, self.remote_site)
|
||||
|
||||
def get_column(self, index):
|
||||
"""
|
||||
Returns info about column
|
||||
|
||||
Args:
|
||||
index (QModelIndex)
|
||||
Returns:
|
||||
(tuple): (COLUMN_NAME: COLUMN_LABEL)
|
||||
"""
|
||||
return self.COLUMN_LABELS[index]
|
||||
|
||||
def get_header_index(self, value):
|
||||
|
|
@ -108,8 +134,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
actually queried (scrolled a couple of times to list more
|
||||
than single page of records)
|
||||
"""
|
||||
if self.sync_server.is_paused() or \
|
||||
self.sync_server.is_project_paused(self.project):
|
||||
if self.is_editing or not self.is_running:
|
||||
return
|
||||
self.refresh_started.emit()
|
||||
self.beginResetModel()
|
||||
|
|
@ -191,7 +216,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
self.sort = {self.SORT_BY_COLUMN[index]: order} # reset
|
||||
# add last one
|
||||
for key, val in backup_sort.items():
|
||||
if key != '_id':
|
||||
if key != '_id' and key != self.SORT_BY_COLUMN[index]:
|
||||
self.sort[key] = val
|
||||
break
|
||||
# add default one
|
||||
|
|
@ -363,7 +388,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
"updated_dt_remote", # remote created_dt
|
||||
"files_count", # count of files
|
||||
"files_size", # file size of all files
|
||||
"context.asset", # priority TODO
|
||||
"priority", # priority
|
||||
"status" # status
|
||||
]
|
||||
|
||||
|
|
@ -374,6 +399,8 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
'representation': lib.MultiSelectFilter('representation')
|
||||
}
|
||||
|
||||
EDITABLE_COLUMNS = ["priority"]
|
||||
|
||||
refresh_started = QtCore.Signal()
|
||||
refresh_finished = QtCore.Signal()
|
||||
|
||||
|
|
@ -403,8 +430,8 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
status = attr.ib(default=None)
|
||||
path = attr.ib(default=None)
|
||||
|
||||
def __init__(self, sync_server, header, project=None):
|
||||
super(SyncRepresentationSummaryModel, self).__init__()
|
||||
def __init__(self, sync_server, header, project=None, parent=None):
|
||||
super(SyncRepresentationSummaryModel, self).__init__(parent=parent)
|
||||
self._header = header
|
||||
self._data = []
|
||||
self._project = project
|
||||
|
|
@ -412,10 +439,13 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
self._total_records = 0 # how many documents query actually found
|
||||
self._word_filter = None
|
||||
self._column_filtering = {}
|
||||
self._is_running = False
|
||||
|
||||
self.edit_icon = qtawesome.icon("fa.edit", color="white")
|
||||
self.is_editing = False
|
||||
|
||||
self._word_filter = None
|
||||
|
||||
self._initialized = False
|
||||
if not self._project or self._project == lib.DUMMY_PROJECT:
|
||||
return
|
||||
|
||||
|
|
@ -472,12 +502,17 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
return item.status == lib.STATUS[2] and \
|
||||
item.remote_progress < 1
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
# because of ImageDelegate
|
||||
if header_value in ['remote_site', 'local_site']:
|
||||
return ""
|
||||
|
||||
return attr.asdict(item)[self._header[index.column()]]
|
||||
|
||||
if role == lib.EditIconRole:
|
||||
if self.can_edit and header_value in self.EDITABLE_COLUMNS:
|
||||
return self.edit_icon
|
||||
|
||||
if role == Qt.UserRole:
|
||||
return item._id
|
||||
|
||||
|
|
@ -549,7 +584,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
avg_progress_remote,
|
||||
repre.get("files_count", 1),
|
||||
lib.pretty_size(repre.get("files_size", 0)),
|
||||
1,
|
||||
repre.get("priority"),
|
||||
lib.STATUS[repre.get("status", -1)],
|
||||
files[0].get('path')
|
||||
)
|
||||
|
|
@ -668,6 +703,16 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
'$cond': [{'$size': "$order_local.paused"},
|
||||
1,
|
||||
0]},
|
||||
'priority': {
|
||||
'$cond': [
|
||||
{'$size': '$order_local.priority'},
|
||||
{'$first': '$order_local.priority'},
|
||||
{'$cond': [
|
||||
{'$size': '$order_remote.priority'},
|
||||
{'$first': '$order_remote.priority'},
|
||||
self.sync_server.DEFAULT_PRIORITY]}
|
||||
]
|
||||
},
|
||||
}},
|
||||
{'$group': {
|
||||
'_id': '$_id',
|
||||
|
|
@ -690,7 +735,8 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
'failed_local_tries': {'$sum': '$failed_local_tries'},
|
||||
'paused_remote': {'$sum': '$paused_remote'},
|
||||
'paused_local': {'$sum': '$paused_local'},
|
||||
'updated_dt_local': {'$max': "$updated_dt_local"}
|
||||
'updated_dt_local': {'$max': "$updated_dt_local"},
|
||||
'priority': {'$max': "$priority"},
|
||||
}},
|
||||
{"$project": self.projection}
|
||||
]
|
||||
|
|
@ -772,6 +818,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
'updated_dt_local': 1,
|
||||
'paused_remote': 1,
|
||||
'paused_local': 1,
|
||||
'priority': 1,
|
||||
'status': {
|
||||
'$switch': {
|
||||
'branches': [
|
||||
|
|
@ -818,6 +865,35 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
|
|||
}
|
||||
}
|
||||
|
||||
def set_priority_data(self, index, value):
|
||||
"""
|
||||
Sets 'priority' flag and value on local site for selected reprs.
|
||||
|
||||
Args:
|
||||
index (QItemIndex): selected index from View
|
||||
value (int): priority value
|
||||
|
||||
Updates DB.
|
||||
Potentially should allow set priority to any site when user
|
||||
management is implemented.
|
||||
"""
|
||||
if not self.can_edit:
|
||||
return
|
||||
|
||||
repre_id = self.data(index, Qt.UserRole)
|
||||
|
||||
representation = list(self.dbcon.find({"type": "representation",
|
||||
"_id": repre_id}))
|
||||
if representation:
|
||||
self.sync_server.update_db(self.project, None, None,
|
||||
representation.pop(),
|
||||
get_local_site_id(),
|
||||
priority=value)
|
||||
self.is_editing = False
|
||||
|
||||
# all other approaches messed up selection to 0th index
|
||||
self.timer.setInterval(0)
|
||||
|
||||
|
||||
class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
||||
"""
|
||||
|
|
@ -852,7 +928,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
"updated_dt_local", # local created_dt
|
||||
"updated_dt_remote", # remote created_dt
|
||||
"size", # remote progress
|
||||
"size", # priority TODO
|
||||
"priority", # priority
|
||||
"status" # status
|
||||
]
|
||||
|
||||
|
|
@ -861,8 +937,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
'file': lib.RegexTextFilter('file'),
|
||||
}
|
||||
|
||||
refresh_started = QtCore.Signal()
|
||||
refresh_finished = QtCore.Signal()
|
||||
EDITABLE_COLUMNS = ["priority"]
|
||||
|
||||
@attr.s
|
||||
class SyncRepresentationDetail:
|
||||
|
|
@ -898,8 +973,11 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
self._total_records = 0 # how many documents query actually found
|
||||
self._word_filter = None
|
||||
self._id = _id
|
||||
self._initialized = False
|
||||
self._column_filtering = {}
|
||||
self._is_running = False
|
||||
|
||||
self.is_editing = False
|
||||
self.edit_icon = qtawesome.icon("fa.edit", color="white")
|
||||
|
||||
self.sync_server = sync_server
|
||||
# TODO think about admin mode
|
||||
|
|
@ -952,11 +1030,17 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
return item.status == lib.STATUS[2] and \
|
||||
item.remote_progress < 1
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
# because of ImageDelegate
|
||||
if header_value in ['remote_site', 'local_site']:
|
||||
return ""
|
||||
|
||||
return attr.asdict(item)[self._header[index.column()]]
|
||||
|
||||
if role == lib.EditIconRole:
|
||||
if self.can_edit and header_value in self.EDITABLE_COLUMNS:
|
||||
return self.edit_icon
|
||||
|
||||
if role == Qt.UserRole:
|
||||
return item._id
|
||||
|
||||
|
|
@ -1026,7 +1110,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
local_progress,
|
||||
remote_progress,
|
||||
lib.pretty_size(file.get('size', 0)),
|
||||
1,
|
||||
repre.get("priority"),
|
||||
lib.STATUS[repre.get("status", -1)],
|
||||
repre.get("tries"),
|
||||
'\n'.join(errors),
|
||||
|
|
@ -1144,7 +1228,17 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
"$order_remote.tries",
|
||||
[]
|
||||
]}
|
||||
]}}
|
||||
]}},
|
||||
'priority': {
|
||||
'$cond': [
|
||||
{'$size': '$order_local.priority'},
|
||||
{'$first': '$order_local.priority'},
|
||||
{'$cond': [
|
||||
{'$size': '$order_remote.priority'},
|
||||
{'$first': '$order_remote.priority'},
|
||||
self.sync_server.DEFAULT_PRIORITY]}
|
||||
]
|
||||
},
|
||||
}},
|
||||
{"$project": self.projection}
|
||||
]
|
||||
|
|
@ -1210,6 +1304,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
'failed_remote_error': 1,
|
||||
'failed_local_error': 1,
|
||||
'tries': 1,
|
||||
'priority': 1,
|
||||
'status': {
|
||||
'$switch': {
|
||||
'branches': [
|
||||
|
|
@ -1261,3 +1356,37 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
|
|||
},
|
||||
'data.path': 1
|
||||
}
|
||||
|
||||
def set_priority_data(self, index, value):
|
||||
"""
|
||||
Sets 'priority' flag and value on local site for selected reprs.
|
||||
|
||||
Args:
|
||||
index (QItemIndex): selected index from View
|
||||
value (int): priority value
|
||||
|
||||
Updates DB
|
||||
"""
|
||||
if not self.can_edit:
|
||||
return
|
||||
|
||||
file_id = self.data(index, Qt.UserRole)
|
||||
|
||||
updated_file = None
|
||||
# conversion from cursor to list
|
||||
representations = list(self.dbcon.find({"type": "representation",
|
||||
"_id": self._id}))
|
||||
|
||||
representation = representations.pop()
|
||||
for repre_file in representation["files"]:
|
||||
if repre_file["_id"] == file_id:
|
||||
updated_file = repre_file
|
||||
break
|
||||
|
||||
if representation and updated_file:
|
||||
self.sync_server.update_db(self.project, None, updated_file,
|
||||
representation, get_local_site_id(),
|
||||
priority=value)
|
||||
self.is_editing = False
|
||||
# all other approaches messed up selection to 0th index
|
||||
self.timer.setInterval(0)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from openpype.modules.sync_server.tray.models import (
|
|||
)
|
||||
|
||||
from openpype.modules.sync_server.tray import lib
|
||||
from openpype.modules.sync_server.tray import delegates
|
||||
|
||||
log = PypeLogger().get_logger("SyncServer")
|
||||
|
||||
|
|
@ -94,16 +95,19 @@ class SyncProjectListWidget(ProjectListWidget):
|
|||
|
||||
self.project_name = point_index.data(QtCore.Qt.DisplayRole)
|
||||
|
||||
menu = QtWidgets.QMenu()
|
||||
menu = QtWidgets.QMenu(self)
|
||||
actions_mapping = {}
|
||||
|
||||
if self.sync_server.is_project_paused(self.project_name):
|
||||
action = QtWidgets.QAction("Unpause")
|
||||
actions_mapping[action] = self._unpause
|
||||
else:
|
||||
action = QtWidgets.QAction("Pause")
|
||||
actions_mapping[action] = self._pause
|
||||
menu.addAction(action)
|
||||
can_edit = self.model.can_edit
|
||||
|
||||
if can_edit:
|
||||
if self.sync_server.is_project_paused(self.project_name):
|
||||
action = QtWidgets.QAction("Unpause")
|
||||
actions_mapping[action] = self._unpause
|
||||
else:
|
||||
action = QtWidgets.QAction("Pause")
|
||||
actions_mapping[action] = self._pause
|
||||
menu.addAction(action)
|
||||
|
||||
if self.local_site == get_local_site_id():
|
||||
action = QtWidgets.QAction("Clear local project")
|
||||
|
|
@ -145,10 +149,10 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
|
||||
def _selection_changed(self, _new_selected, _all_selected):
|
||||
idxs = self.selection_model.selectedRows()
|
||||
self._selected_ids = []
|
||||
self._selected_ids = set()
|
||||
|
||||
for index in idxs:
|
||||
self._selected_ids.append(self.model.data(index, Qt.UserRole))
|
||||
self._selected_ids.add(self.model.data(index, Qt.UserRole))
|
||||
|
||||
def _set_selection(self):
|
||||
"""
|
||||
|
|
@ -156,14 +160,14 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
|
||||
Keep selection during model refresh.
|
||||
"""
|
||||
existing_ids = []
|
||||
existing_ids = set()
|
||||
for selected_id in self._selected_ids:
|
||||
index = self.model.get_index(selected_id)
|
||||
if index and index.isValid():
|
||||
mode = QtCore.QItemSelectionModel.Select | \
|
||||
QtCore.QItemSelectionModel.Rows
|
||||
self.selection_model.select(index, mode)
|
||||
existing_ids.append(selected_id)
|
||||
existing_ids.add(selected_id)
|
||||
|
||||
self._selected_ids = existing_ids
|
||||
|
||||
|
|
@ -171,9 +175,17 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
"""
|
||||
Opens representation dialog with all files after doubleclick
|
||||
"""
|
||||
# priority editing
|
||||
if self.model.can_edit:
|
||||
column_name = self.model.get_column(index.column())
|
||||
if column_name[0] in self.model.EDITABLE_COLUMNS:
|
||||
self.model.is_editing = True
|
||||
self.table_view.openPersistentEditor(index)
|
||||
return
|
||||
|
||||
_id = self.model.data(index, Qt.UserRole)
|
||||
detail_window = SyncServerDetailWindow(
|
||||
self.sync_server, _id, self.model.project)
|
||||
self.sync_server, _id, self.model.project, parent=self)
|
||||
detail_window.exec()
|
||||
|
||||
def _on_context_menu(self, point):
|
||||
|
|
@ -189,13 +201,15 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
return
|
||||
|
||||
if is_multi:
|
||||
index = self.model.get_index(self._selected_ids[0])
|
||||
index = self.model.get_index(list(self._selected_ids)[0])
|
||||
item = self.model.data(index, lib.FullItemRole)
|
||||
else:
|
||||
item = self.model.data(point_index, lib.FullItemRole)
|
||||
|
||||
can_edit = self.model.can_edit
|
||||
action_kwarg_map, actions_mapping, menu = self._prepare_menu(item,
|
||||
is_multi)
|
||||
is_multi,
|
||||
can_edit)
|
||||
|
||||
result = menu.exec_(QtGui.QCursor.pos())
|
||||
if result:
|
||||
|
|
@ -206,8 +220,8 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
|
||||
self.model.refresh()
|
||||
|
||||
def _prepare_menu(self, item, is_multi):
|
||||
menu = QtWidgets.QMenu()
|
||||
def _prepare_menu(self, item, is_multi, can_edit):
|
||||
menu = QtWidgets.QMenu(self)
|
||||
|
||||
actions_mapping = {}
|
||||
action_kwarg_map = {}
|
||||
|
|
@ -235,24 +249,30 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
self._get_action_kwargs(site)
|
||||
menu.addAction(action)
|
||||
|
||||
if remote_progress == 1.0 or is_multi:
|
||||
if can_edit and (remote_progress == 1.0 or is_multi):
|
||||
action = QtWidgets.QAction("Re-sync Active site")
|
||||
action_kwarg_map[action] = self._get_action_kwargs(active_site)
|
||||
actions_mapping[action] = self._reset_site
|
||||
menu.addAction(action)
|
||||
|
||||
if local_progress == 1.0 or is_multi:
|
||||
if can_edit and (local_progress == 1.0 or is_multi):
|
||||
action = QtWidgets.QAction("Re-sync Remote site")
|
||||
action_kwarg_map[action] = self._get_action_kwargs(remote_site)
|
||||
actions_mapping[action] = self._reset_site
|
||||
menu.addAction(action)
|
||||
|
||||
if active_site == get_local_site_id():
|
||||
if can_edit and active_site == get_local_site_id():
|
||||
action = QtWidgets.QAction("Completely remove from local")
|
||||
action_kwarg_map[action] = self._get_action_kwargs(active_site)
|
||||
actions_mapping[action] = self._remove_site
|
||||
menu.addAction(action)
|
||||
|
||||
if can_edit:
|
||||
action = QtWidgets.QAction("Change priority")
|
||||
action_kwarg_map[action] = self._get_action_kwargs(active_site)
|
||||
actions_mapping[action] = self._change_priority
|
||||
menu.addAction(action)
|
||||
|
||||
# # temp for testing only !!!
|
||||
# action = QtWidgets.QAction("Download")
|
||||
# action_kwarg_map[action] = self._get_action_kwargs(active_site)
|
||||
|
|
@ -397,6 +417,16 @@ class _SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
except OSError:
|
||||
raise OSError('unsupported xdg-open call??')
|
||||
|
||||
def _change_priority(self, **kwargs):
|
||||
"""Open editor to change priority on first selected row"""
|
||||
if self._selected_ids:
|
||||
# get_index returns dummy index with column equals to 0
|
||||
index = self.model.get_index(list(self._selected_ids)[0])
|
||||
column_no = self.model.get_header_index("priority") # real column
|
||||
real_index = self.model.index(index.row(), column_no)
|
||||
self.model.is_editing = True
|
||||
self.table_view.openPersistentEditor(real_index)
|
||||
|
||||
def _get_progress(self, item, site_name, opposite=False):
|
||||
"""Returns progress value according to site (side)"""
|
||||
progress = {'local': item.local_progress,
|
||||
|
|
@ -441,7 +471,7 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget):
|
|||
|
||||
self.sync_server = sync_server
|
||||
|
||||
self._selected_ids = [] # keep last selected _id
|
||||
self._selected_ids = set() # keep last selected _id
|
||||
|
||||
txt_filter = QtWidgets.QLineEdit()
|
||||
txt_filter.setPlaceholderText("Quick filter representations..")
|
||||
|
|
@ -459,7 +489,8 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget):
|
|||
table_view = QtWidgets.QTableView()
|
||||
headers = [item[0] for item in self.default_widths]
|
||||
|
||||
model = SyncRepresentationSummaryModel(sync_server, headers, project)
|
||||
model = SyncRepresentationSummaryModel(sync_server, headers, project,
|
||||
parent=self)
|
||||
table_view.setModel(model)
|
||||
table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
table_view.setSelectionMode(
|
||||
|
|
@ -470,15 +501,20 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget):
|
|||
-1, Qt.AscendingOrder)
|
||||
table_view.setAlternatingRowColors(True)
|
||||
table_view.verticalHeader().hide()
|
||||
table_view.viewport().setAttribute(QtCore.Qt.WA_Hover, True)
|
||||
|
||||
column = table_view.model().get_header_index("local_site")
|
||||
delegate = ImageDelegate(self)
|
||||
delegate = delegates.ImageDelegate(self)
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
column = table_view.model().get_header_index("remote_site")
|
||||
delegate = ImageDelegate(self)
|
||||
delegate = delegates.ImageDelegate(self)
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
column = table_view.model().get_header_index("priority")
|
||||
priority_delegate = delegates.PriorityDelegate(self)
|
||||
table_view.setItemDelegateForColumn(column, priority_delegate)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(top_bar_layout)
|
||||
|
|
@ -508,18 +544,19 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget):
|
|||
self.selection_model = self.table_view.selectionModel()
|
||||
self.selection_model.selectionChanged.connect(self._selection_changed)
|
||||
|
||||
def _prepare_menu(self, item, is_multi):
|
||||
def _prepare_menu(self, item, is_multi, can_edit):
|
||||
action_kwarg_map, actions_mapping, menu = \
|
||||
super()._prepare_menu(item, is_multi)
|
||||
super()._prepare_menu(item, is_multi, can_edit)
|
||||
|
||||
if item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi:
|
||||
if can_edit and (
|
||||
item.status in [lib.STATUS[0], lib.STATUS[1]] or is_multi):
|
||||
action = QtWidgets.QAction("Pause in queue")
|
||||
actions_mapping[action] = self._pause
|
||||
# pause handles which site_name it will pause itself
|
||||
action_kwarg_map[action] = {"selected_ids": self._selected_ids}
|
||||
menu.addAction(action)
|
||||
|
||||
if item.status == lib.STATUS[3] or is_multi:
|
||||
if can_edit and (item.status == lib.STATUS[3] or is_multi):
|
||||
action = QtWidgets.QAction("Unpause in queue")
|
||||
actions_mapping[action] = self._unpause
|
||||
action_kwarg_map[action] = {"selected_ids": self._selected_ids}
|
||||
|
|
@ -598,7 +635,7 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
|||
self.sync_server = sync_server
|
||||
|
||||
self.representation_id = _id
|
||||
self._selected_ids = []
|
||||
self._selected_ids = set()
|
||||
|
||||
self.txt_filter = QtWidgets.QLineEdit()
|
||||
self.txt_filter.setPlaceholderText("Quick filter representation..")
|
||||
|
|
@ -616,6 +653,8 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
|||
|
||||
model = SyncRepresentationDetailModel(sync_server, headers, _id,
|
||||
project)
|
||||
model.is_running = True
|
||||
|
||||
table_view.setModel(model)
|
||||
table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
table_view.setSelectionMode(
|
||||
|
|
@ -628,13 +667,18 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
|||
table_view.verticalHeader().hide()
|
||||
|
||||
column = model.get_header_index("local_site")
|
||||
delegate = ImageDelegate(self)
|
||||
delegate = delegates.ImageDelegate(self)
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
column = model.get_header_index("remote_site")
|
||||
delegate = ImageDelegate(self)
|
||||
delegate = delegates.ImageDelegate(self)
|
||||
table_view.setItemDelegateForColumn(column, delegate)
|
||||
|
||||
if model.can_edit:
|
||||
column = table_view.model().get_header_index("priority")
|
||||
priority_delegate = delegates.PriorityDelegate(self)
|
||||
table_view.setItemDelegateForColumn(column, priority_delegate)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(top_bar_layout)
|
||||
|
|
@ -658,12 +702,25 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
|||
|
||||
self.txt_filter.textChanged.connect(lambda: model.set_word_filter(
|
||||
self.txt_filter.text()))
|
||||
table_view.doubleClicked.connect(self._double_clicked)
|
||||
table_view.customContextMenuRequested.connect(self._on_context_menu)
|
||||
|
||||
model.refresh_started.connect(self._save_scrollbar)
|
||||
model.refresh_finished.connect(self._set_scrollbar)
|
||||
model.modelReset.connect(self._set_selection)
|
||||
|
||||
def _double_clicked(self, index):
|
||||
"""
|
||||
Opens representation dialog with all files after doubleclick
|
||||
"""
|
||||
# priority editing
|
||||
if self.model.can_edit:
|
||||
column_name = self.model.get_column(index.column())
|
||||
if column_name[0] in self.model.EDITABLE_COLUMNS:
|
||||
self.model.is_editing = True
|
||||
self.table_view.openPersistentEditor(index)
|
||||
return
|
||||
|
||||
def _show_detail(self, selected_ids=None):
|
||||
"""
|
||||
Shows windows with error message for failed sync of a file.
|
||||
|
|
@ -672,10 +729,10 @@ class SyncRepresentationDetailWidget(_SyncRepresentationWidget):
|
|||
|
||||
detail_window.exec()
|
||||
|
||||
def _prepare_menu(self, item, is_multi):
|
||||
def _prepare_menu(self, item, is_multi, can_edit):
|
||||
"""Adds view (and model) dependent actions to default ones"""
|
||||
action_kwarg_map, actions_mapping, menu = \
|
||||
super()._prepare_menu(item, is_multi)
|
||||
super()._prepare_menu(item, is_multi, can_edit)
|
||||
|
||||
if item.status == lib.STATUS[2] or is_multi:
|
||||
action = QtWidgets.QAction("Open error detail")
|
||||
|
|
@ -778,72 +835,6 @@ class SyncRepresentationErrorWidget(QtWidgets.QWidget):
|
|||
layout.addWidget(text_area)
|
||||
|
||||
|
||||
class ImageDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""
|
||||
Prints icon of site and progress of synchronization
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ImageDelegate, self).__init__(parent)
|
||||
self.icons = {}
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
super(ImageDelegate, self).paint(painter, option, index)
|
||||
option = QtWidgets.QStyleOptionViewItem(option)
|
||||
option.showDecorationSelected = True
|
||||
|
||||
provider = index.data(lib.ProviderRole)
|
||||
value = index.data(lib.ProgressRole)
|
||||
date_value = index.data(lib.DateRole)
|
||||
is_failed = index.data(lib.FailedRole)
|
||||
|
||||
if not self.icons.get(provider):
|
||||
resource_path = os.path.dirname(__file__)
|
||||
resource_path = os.path.join(resource_path, "..",
|
||||
"providers", "resources")
|
||||
pix_url = "{}/{}.png".format(resource_path, provider)
|
||||
pixmap = QtGui.QPixmap(pix_url)
|
||||
self.icons[provider] = pixmap
|
||||
else:
|
||||
pixmap = self.icons[provider]
|
||||
|
||||
padding = 10
|
||||
point = QtCore.QPoint(option.rect.x() + padding,
|
||||
option.rect.y() +
|
||||
(option.rect.height() - pixmap.height()) / 2)
|
||||
painter.drawPixmap(point, pixmap)
|
||||
|
||||
overlay_rect = option.rect.translated(0, 0)
|
||||
overlay_rect.setHeight(overlay_rect.height() * (1.0 - float(value)))
|
||||
painter.fillRect(overlay_rect,
|
||||
QtGui.QBrush(QtGui.QColor(0, 0, 0, 100)))
|
||||
text_rect = option.rect.translated(10, 0)
|
||||
painter.drawText(text_rect,
|
||||
QtCore.Qt.AlignCenter,
|
||||
date_value)
|
||||
|
||||
if is_failed:
|
||||
overlay_rect = option.rect.translated(0, 0)
|
||||
painter.fillRect(overlay_rect,
|
||||
QtGui.QBrush(QtGui.QColor(255, 0, 0, 35)))
|
||||
|
||||
|
||||
class TransparentWidget(QtWidgets.QWidget):
|
||||
"""Used for header cell for resizing to work properly"""
|
||||
clicked = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, column_name, *args, **kwargs):
|
||||
super(TransparentWidget, self).__init__(*args, **kwargs)
|
||||
self.column_name = column_name
|
||||
# self.setStyleSheet("background: red;")
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.clicked.emit(self.column_name)
|
||||
|
||||
super(TransparentWidget, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class HorizontalHeader(QtWidgets.QHeaderView):
|
||||
"""Reiplemented QHeaderView to contain clickable changeable button"""
|
||||
def __init__(self, parent=None):
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ class CollectCurrentUserPype(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
user = get_openpype_username()
|
||||
context.data["user"] = user
|
||||
self.log.debug("Colected user \"{}\"".format(user))
|
||||
self.log.debug("Collected user \"{}\"".format(user))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class CollectSceneVersion(pyblish.api.ContextPlugin):
|
|||
if "unreal" in pyblish.api.registered_hosts():
|
||||
return
|
||||
|
||||
assert context.data.get('currentFile'), "Cannot get curren file"
|
||||
assert context.data.get('currentFile'), "Cannot get current file"
|
||||
filename = os.path.basename(context.data.get('currentFile'))
|
||||
|
||||
if '<shell>' in filename:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import re
|
|||
import json
|
||||
import copy
|
||||
import tempfile
|
||||
import clique
|
||||
|
||||
import openpype
|
||||
import openpype.api
|
||||
|
|
@ -114,8 +115,30 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
# Prepare burnin options
|
||||
profile_options = copy.deepcopy(self.default_options)
|
||||
for key, value in (self.options or {}).items():
|
||||
if value is not None:
|
||||
profile_options[key] = value
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if key == "bg_color" and len(value) == 4:
|
||||
bg_red, bg_green, bg_blue, bg_alpha = value
|
||||
bg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format(
|
||||
bg_red, bg_green, bg_blue
|
||||
)
|
||||
bg_color_alpha = float(bg_alpha) / 255
|
||||
profile_options["bg_opacity"] = bg_color_alpha
|
||||
profile_options["bg_color"] = bg_color_hex
|
||||
continue
|
||||
|
||||
elif key == "font_color" and len(value) == 4:
|
||||
fg_red, fg_green, fg_blue, fg_alpha = value
|
||||
fg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format(
|
||||
fg_red, fg_green, fg_blue
|
||||
)
|
||||
fg_color_alpha = float(fg_alpha) / 255
|
||||
profile_options["opacity"] = fg_color_alpha
|
||||
profile_options["font_color"] = fg_color_hex
|
||||
continue
|
||||
|
||||
profile_options[key] = value
|
||||
|
||||
# Prepare global burnin values from presets
|
||||
profile_burnins = {}
|
||||
|
|
@ -247,7 +270,9 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
"output": temp_data["full_output_path"],
|
||||
"burnin_data": burnin_data,
|
||||
"options": burnin_options,
|
||||
"values": burnin_values
|
||||
"values": burnin_values,
|
||||
"full_input_path": temp_data["full_input_paths"][0],
|
||||
"first_frame": temp_data["first_frame"]
|
||||
}
|
||||
|
||||
self.log.debug(
|
||||
|
|
@ -461,32 +486,47 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
None: This is processing method.
|
||||
"""
|
||||
# TODO we should find better way to know if input is sequence
|
||||
is_sequence = (
|
||||
"sequence" in new_repre["tags"]
|
||||
and isinstance(new_repre["files"], (tuple, list))
|
||||
)
|
||||
input_filenames = new_repre["files"]
|
||||
is_sequence = False
|
||||
if isinstance(input_filenames, (tuple, list)):
|
||||
if len(input_filenames) > 1:
|
||||
is_sequence = True
|
||||
|
||||
# Sequence must have defined first frame
|
||||
# - not used if input is not a sequence
|
||||
first_frame = None
|
||||
if is_sequence:
|
||||
input_filename = new_repre["sequence_file"]
|
||||
else:
|
||||
input_filename = new_repre["files"]
|
||||
collections, _ = clique.assemble(input_filenames)
|
||||
if not collections:
|
||||
is_sequence = False
|
||||
else:
|
||||
input_filename = new_repre["sequence_file"]
|
||||
collection = collections[0]
|
||||
indexes = list(collection.indexes)
|
||||
padding = len(str(max(indexes)))
|
||||
head = collection.format("{head}")
|
||||
tail = collection.format("{tail}")
|
||||
output_filename = "{}%{:0>2}d{}{}".format(
|
||||
head, padding, filename_suffix, tail
|
||||
)
|
||||
repre_files = []
|
||||
for idx in indexes:
|
||||
repre_files.append(output_filename % idx)
|
||||
|
||||
filepart_start, ext = os.path.splitext(input_filename)
|
||||
dir_path, basename = os.path.split(filepart_start)
|
||||
first_frame = min(indexes)
|
||||
|
||||
if is_sequence:
|
||||
# NOTE modified to keep name when multiple dots are in name
|
||||
basename_parts = basename.split(".")
|
||||
frame_part = basename_parts.pop(-1)
|
||||
if not is_sequence:
|
||||
input_filename = input_filenames
|
||||
if isinstance(input_filename, (tuple, list)):
|
||||
input_filename = input_filename[0]
|
||||
|
||||
basename_start = ".".join(basename_parts) + filename_suffix
|
||||
new_basename = ".".join((basename_start, frame_part))
|
||||
output_filename = new_basename + ext
|
||||
|
||||
else:
|
||||
filepart_start, ext = os.path.splitext(input_filename)
|
||||
dir_path, basename = os.path.split(filepart_start)
|
||||
output_filename = basename + filename_suffix + ext
|
||||
if dir_path:
|
||||
output_filename = os.path.join(dir_path, output_filename)
|
||||
|
||||
if dir_path:
|
||||
output_filename = os.path.join(dir_path, output_filename)
|
||||
repre_files = output_filename
|
||||
|
||||
stagingdir = new_repre["stagingDir"]
|
||||
full_input_path = os.path.join(
|
||||
|
|
@ -498,6 +538,9 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
|
||||
temp_data["full_input_path"] = full_input_path
|
||||
temp_data["full_output_path"] = full_output_path
|
||||
temp_data["first_frame"] = first_frame
|
||||
|
||||
new_repre["files"] = repre_files
|
||||
|
||||
self.log.debug("full_input_path: {}".format(full_input_path))
|
||||
self.log.debug("full_output_path: {}".format(full_output_path))
|
||||
|
|
@ -505,17 +548,16 @@ class ExtractBurnin(openpype.api.Extractor):
|
|||
# Prepare full paths to input files and filenames for reprensetation
|
||||
full_input_paths = []
|
||||
if is_sequence:
|
||||
repre_files = []
|
||||
for frame_index in range(1, temp_data["duration"] + 1):
|
||||
repre_files.append(output_filename % frame_index)
|
||||
full_input_paths.append(full_input_path % frame_index)
|
||||
for filename in input_filenames:
|
||||
filepath = os.path.join(
|
||||
os.path.normpath(stagingdir), filename
|
||||
).replace("\\", "/")
|
||||
full_input_paths.append(filepath)
|
||||
|
||||
else:
|
||||
full_input_paths.append(full_input_path)
|
||||
repre_files = output_filename
|
||||
|
||||
temp_data["full_input_paths"] = full_input_paths
|
||||
new_repre["files"] = repre_files
|
||||
|
||||
def prepare_repre_data(self, instance, repre, burnin_data, temp_data):
|
||||
"""Prepare data for representation.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ ffprobe_path = openpype.lib.get_ffmpeg_tool_path("ffprobe")
|
|||
|
||||
|
||||
FFMPEG = (
|
||||
'"{}" -i "%(input)s" %(filters)s %(args)s%(output)s'
|
||||
'"{}"%(input_args)s -i "%(input)s" %(filters)s %(args)s%(output)s'
|
||||
).format(ffmpeg_path)
|
||||
|
||||
FFPROBE = (
|
||||
|
|
@ -121,10 +121,18 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
'font_size': 42
|
||||
}
|
||||
|
||||
def __init__(self, source, streams=None, options_init=None):
|
||||
def __init__(
|
||||
self, source, streams=None, options_init=None, first_frame=None
|
||||
):
|
||||
if not streams:
|
||||
streams = _streams(source)
|
||||
|
||||
input_args = []
|
||||
if first_frame:
|
||||
input_args.append("-start_number {}".format(first_frame))
|
||||
|
||||
self.input_args = input_args
|
||||
|
||||
super().__init__(source, streams)
|
||||
|
||||
if options_init:
|
||||
|
|
@ -289,7 +297,12 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
if self.filter_string:
|
||||
filters = '-vf "{}"'.format(self.filter_string)
|
||||
|
||||
input_args = ""
|
||||
if self.input_args:
|
||||
input_args = " {}".format(" ".join(self.input_args))
|
||||
|
||||
return (FFMPEG % {
|
||||
'input_args': input_args,
|
||||
'input': self.source,
|
||||
'output': output,
|
||||
'args': '%s ' % args if args else '',
|
||||
|
|
@ -370,7 +383,8 @@ def example(input_path, output_path):
|
|||
|
||||
def burnins_from_data(
|
||||
input_path, output_path, data,
|
||||
codec_data=None, options=None, burnin_values=None, overwrite=True
|
||||
codec_data=None, options=None, burnin_values=None, overwrite=True,
|
||||
full_input_path=None, first_frame=None
|
||||
):
|
||||
"""This method adds burnins to video/image file based on presets setting.
|
||||
|
||||
|
|
@ -427,8 +441,11 @@ def burnins_from_data(
|
|||
"shot": "sh0010"
|
||||
}
|
||||
"""
|
||||
streams = None
|
||||
if full_input_path:
|
||||
streams = _streams(full_input_path)
|
||||
|
||||
burnin = ModifiedBurnins(input_path, options_init=options)
|
||||
burnin = ModifiedBurnins(input_path, streams, options, first_frame)
|
||||
|
||||
frame_start = data.get("frame_start")
|
||||
frame_end = data.get("frame_end")
|
||||
|
|
@ -591,6 +608,8 @@ if __name__ == "__main__":
|
|||
in_data["burnin_data"],
|
||||
codec_data=in_data.get("codec"),
|
||||
options=in_data.get("options"),
|
||||
burnin_values=in_data.get("values")
|
||||
burnin_values=in_data.get("values"),
|
||||
full_input_path=in_data.get("full_input_path"),
|
||||
first_frame=in_data.get("first_frame")
|
||||
)
|
||||
print("* Burnin script has finished")
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"harmony/20",
|
||||
"photoshop/2021",
|
||||
"aftereffects/2021",
|
||||
"unreal/4-24"
|
||||
"unreal/4-26"
|
||||
],
|
||||
"tools_env": []
|
||||
}
|
||||
|
|
@ -21,8 +21,8 @@
|
|||
"secondary_pool": "",
|
||||
"group": "",
|
||||
"department": "",
|
||||
"limit_groups": {},
|
||||
"use_gpu": true
|
||||
"use_gpu": true,
|
||||
"limit_groups": {}
|
||||
},
|
||||
"HarmonySubmitDeadline": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
|
|
@ -73,8 +73,18 @@
|
|||
"enabled": true,
|
||||
"options": {
|
||||
"font_size": 42,
|
||||
"opacity": 1.0,
|
||||
"bg_opacity": 0.5,
|
||||
"font_color": [
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
"bg_color": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
127
|
||||
],
|
||||
"x_offset": 5,
|
||||
"y_offset": 5,
|
||||
"bg_padding": 5
|
||||
|
|
|
|||
|
|
@ -21,10 +21,20 @@
|
|||
"LoadClip": {
|
||||
"enabled": true,
|
||||
"families": [
|
||||
"render2d", "source", "plate", "render", "review"
|
||||
"render2d",
|
||||
"source",
|
||||
"plate",
|
||||
"render",
|
||||
"review"
|
||||
],
|
||||
"representations": [
|
||||
"exr", "dpx", "jpg", "jpeg", "png", "h264", "mov"
|
||||
"exr",
|
||||
"dpx",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"png",
|
||||
"h264",
|
||||
"mov"
|
||||
],
|
||||
"clip_name_template": "{asset}_{subset}_{representation}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,11 +210,11 @@
|
|||
"environment": {}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"13-0": "13.0 (Testing only)",
|
||||
"12-2": "12.2",
|
||||
"12-0": "12.0",
|
||||
"11-3": "11.3",
|
||||
"11-2": "11.2",
|
||||
"13-0": "13.0 (Testing only)"
|
||||
"11-2": "11.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -354,11 +354,11 @@
|
|||
"environment": {}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"13-0": "13.0 (Testing only)",
|
||||
"12-2": "12.2",
|
||||
"12-0": "12.0",
|
||||
"11-3": "11.3",
|
||||
"11-2": "11.2",
|
||||
"13-0": "13.0 (Testing only)"
|
||||
"11-2": "11.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -496,11 +496,11 @@
|
|||
"environment": {}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"13-0": "13.0 (Testing only)",
|
||||
"12-2": "12.2",
|
||||
"12-0": "12.0",
|
||||
"11-3": "11.3",
|
||||
"11-2": "11.2",
|
||||
"13-0": "13.0 (Testing only)"
|
||||
"11-2": "11.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -640,11 +640,11 @@
|
|||
"environment": {}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"13-0": "13.0 (Testing only)",
|
||||
"12-2": "12.2",
|
||||
"12-0": "12.0",
|
||||
"11-3": "11.3",
|
||||
"11-2": "11.2",
|
||||
"13-0": "13.0 (Testing only)"
|
||||
"11-2": "11.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
{
|
||||
"studio_name": "Studio name",
|
||||
"studio_code": "stu",
|
||||
"admin_password": "",
|
||||
"environment": {
|
||||
"OPENPYPE_OCIO_CONFIG": "{STUDIO_SOFT}/OpenColorIO-Configs",
|
||||
"__environment_keys__": {
|
||||
"global": [
|
||||
"OPENPYPE_OCIO_CONFIG"
|
||||
]
|
||||
"global": []
|
||||
}
|
||||
},
|
||||
"openpype_path": {
|
||||
|
|
|
|||
|
|
@ -457,27 +457,18 @@ class BaseItemEntity(BaseEntity):
|
|||
pass
|
||||
|
||||
@property
|
||||
def can_discard_changes(self):
|
||||
"""Result defines if `discard_changes` will be processed.
|
||||
|
||||
Also can be used as validation before the method is called.
|
||||
"""
|
||||
def _can_discard_changes(self):
|
||||
"""Defines if `discard_changes` will be processed."""
|
||||
return self.has_unsaved_changes
|
||||
|
||||
@property
|
||||
def can_add_to_studio_default(self):
|
||||
"""Result defines if `add_to_studio_default` will be processed.
|
||||
|
||||
Also can be used as validation before the method is called.
|
||||
"""
|
||||
def _can_add_to_studio_default(self):
|
||||
"""Defines if `add_to_studio_default` will be processed."""
|
||||
if self._override_state is not OverrideState.STUDIO:
|
||||
return False
|
||||
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return False
|
||||
|
||||
# Skip if entity is under group
|
||||
if self.group_item:
|
||||
if self.group_item is not None:
|
||||
return False
|
||||
|
||||
# Skip if is group and any children is already marked with studio
|
||||
|
|
@ -487,36 +478,24 @@ class BaseItemEntity(BaseEntity):
|
|||
return True
|
||||
|
||||
@property
|
||||
def can_remove_from_studio_default(self):
|
||||
"""Result defines if `remove_from_studio_default` can be triggered.
|
||||
|
||||
This can be also used as validation before the method is called.
|
||||
"""
|
||||
def _can_remove_from_studio_default(self):
|
||||
"""Defines if `remove_from_studio_default` can be processed."""
|
||||
if self._override_state is not OverrideState.STUDIO:
|
||||
return False
|
||||
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return False
|
||||
|
||||
if not self.has_studio_override:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def can_add_to_project_override(self):
|
||||
"""Result defines if `add_to_project_override` can be triggered.
|
||||
|
||||
Also can be used as validation before the method is called.
|
||||
"""
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return False
|
||||
|
||||
def _can_add_to_project_override(self):
|
||||
"""Defines if `add_to_project_override` can be processed."""
|
||||
# Show only when project overrides are set
|
||||
if self._override_state is not OverrideState.PROJECT:
|
||||
return False
|
||||
|
||||
# Do not show on items under group item
|
||||
if self.group_item:
|
||||
if self.group_item is not None:
|
||||
return False
|
||||
|
||||
# Skip if already is marked to save project overrides
|
||||
|
|
@ -525,14 +504,8 @@ class BaseItemEntity(BaseEntity):
|
|||
return True
|
||||
|
||||
@property
|
||||
def can_remove_from_project_override(self):
|
||||
"""Result defines if `remove_from_project_override` can be triggered.
|
||||
|
||||
This can be also used as validation before the method is called.
|
||||
"""
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return False
|
||||
|
||||
def _can_remove_from_project_override(self):
|
||||
"""Defines if `remove_from_project_override` can be processed."""
|
||||
if self._override_state is not OverrideState.PROJECT:
|
||||
return False
|
||||
|
||||
|
|
@ -544,6 +517,54 @@ class BaseItemEntity(BaseEntity):
|
|||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def can_trigger_discard_changes(self):
|
||||
"""Defines if can trigger `discard_changes`.
|
||||
|
||||
Also can be used as validation before the method is called.
|
||||
"""
|
||||
return self._can_discard_changes
|
||||
|
||||
@property
|
||||
def can_trigger_add_to_studio_default(self):
|
||||
"""Defines if can trigger `add_to_studio_default`.
|
||||
|
||||
Also can be used as validation before the method is called.
|
||||
"""
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return False
|
||||
return self._can_add_to_studio_default
|
||||
|
||||
@property
|
||||
def can_trigger_remove_from_studio_default(self):
|
||||
"""Defines if can trigger `remove_from_studio_default`.
|
||||
|
||||
Also can be used as validation before the method is called.
|
||||
"""
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return False
|
||||
return self._can_remove_from_studio_default
|
||||
|
||||
@property
|
||||
def can_trigger_add_to_project_override(self):
|
||||
"""Defines if can trigger `add_to_project_override`.
|
||||
|
||||
Also can be used as validation before the method is called.
|
||||
"""
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return False
|
||||
return self._can_add_to_project_override
|
||||
|
||||
@property
|
||||
def can_trigger_remove_from_project_override(self):
|
||||
"""Defines if can trigger `remove_from_project_override`.
|
||||
|
||||
Also can be used as validation before the method is called.
|
||||
"""
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return False
|
||||
return self._can_remove_from_project_override
|
||||
|
||||
def discard_changes(self, on_change_trigger=None):
|
||||
"""Discard changes on entity and it's children.
|
||||
|
||||
|
|
@ -568,7 +589,7 @@ class BaseItemEntity(BaseEntity):
|
|||
"""
|
||||
initialized = False
|
||||
if on_change_trigger is None:
|
||||
if not self.can_discard_changes:
|
||||
if not self.can_trigger_discard_changes:
|
||||
return
|
||||
|
||||
initialized = True
|
||||
|
|
@ -588,7 +609,7 @@ class BaseItemEntity(BaseEntity):
|
|||
def add_to_studio_default(self, on_change_trigger=None):
|
||||
initialized = False
|
||||
if on_change_trigger is None:
|
||||
if not self.can_add_to_studio_default:
|
||||
if not self.can_trigger_add_to_studio_default:
|
||||
return
|
||||
|
||||
initialized = True
|
||||
|
|
@ -625,7 +646,7 @@ class BaseItemEntity(BaseEntity):
|
|||
"""
|
||||
initialized = False
|
||||
if on_change_trigger is None:
|
||||
if not self.can_remove_from_studio_default:
|
||||
if not self.can_trigger_remove_from_studio_default:
|
||||
return
|
||||
|
||||
initialized = True
|
||||
|
|
@ -649,7 +670,7 @@ class BaseItemEntity(BaseEntity):
|
|||
def add_to_project_override(self, on_change_trigger=None):
|
||||
initialized = False
|
||||
if on_change_trigger is None:
|
||||
if not self.can_add_to_project_override:
|
||||
if not self.can_trigger_add_to_project_override:
|
||||
return
|
||||
|
||||
initialized = True
|
||||
|
|
@ -689,7 +710,7 @@ class BaseItemEntity(BaseEntity):
|
|||
|
||||
initialized = False
|
||||
if on_change_trigger is None:
|
||||
if not self.can_remove_from_project_override:
|
||||
if not self.can_trigger_remove_from_project_override:
|
||||
return
|
||||
initialized = True
|
||||
on_change_trigger = []
|
||||
|
|
@ -775,7 +796,8 @@ class ItemEntity(BaseItemEntity):
|
|||
# Group item reference
|
||||
if self.parent.is_group:
|
||||
self.group_item = self.parent
|
||||
elif self.parent.group_item:
|
||||
|
||||
elif self.parent.group_item is not None:
|
||||
self.group_item = self.parent.group_item
|
||||
|
||||
self.key = self.schema_data.get("key")
|
||||
|
|
|
|||
|
|
@ -353,6 +353,20 @@ class DictImmutableKeysEntity(ItemEntity):
|
|||
for key in METADATA_KEYS:
|
||||
if key in value:
|
||||
metadata[key] = value.pop(key)
|
||||
|
||||
old_metadata = metadata.get(M_OVERRIDEN_KEY)
|
||||
if old_metadata:
|
||||
old_metadata_set = set(old_metadata)
|
||||
new_metadata = []
|
||||
for key in self.non_gui_children.keys():
|
||||
if key in old_metadata:
|
||||
new_metadata.append(key)
|
||||
old_metadata_set.remove(key)
|
||||
|
||||
for key in old_metadata_set:
|
||||
new_metadata.append(key)
|
||||
metadata[M_OVERRIDEN_KEY] = new_metadata
|
||||
|
||||
return value, metadata
|
||||
|
||||
def update_default_value(self, value):
|
||||
|
|
@ -458,6 +472,9 @@ class DictImmutableKeysEntity(ItemEntity):
|
|||
for child_obj in self.non_gui_children.values():
|
||||
child_obj.add_to_studio_default(on_change_trigger)
|
||||
self._ignore_child_changes = False
|
||||
|
||||
self._update_current_metadata()
|
||||
|
||||
self.parent.on_child_change(self)
|
||||
|
||||
def _remove_from_studio_default(self, on_change_trigger):
|
||||
|
|
@ -471,6 +488,9 @@ class DictImmutableKeysEntity(ItemEntity):
|
|||
for child_obj in self.non_gui_children.values():
|
||||
child_obj.add_to_project_override(_on_change_trigger)
|
||||
self._ignore_child_changes = False
|
||||
|
||||
self._update_current_metadata()
|
||||
|
||||
self.parent.on_child_change(self)
|
||||
|
||||
def _remove_from_project_override(self, on_change_trigger):
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ class DictMutableKeysEntity(EndpointEntity):
|
|||
if self.value_is_env_group:
|
||||
self.item_schema["env_group_key"] = ""
|
||||
|
||||
if not self.group_item:
|
||||
if self.group_item is None:
|
||||
self.is_group = True
|
||||
|
||||
def schema_validations(self):
|
||||
|
|
@ -251,8 +251,18 @@ class DictMutableKeysEntity(EndpointEntity):
|
|||
)
|
||||
raise EntitySchemaError(self, reason)
|
||||
|
||||
for child_obj in self.children_by_key.values():
|
||||
child_obj.schema_validations()
|
||||
# Validate object type schema
|
||||
child_validated = False
|
||||
for child_entity in self.children_by_key.values():
|
||||
child_entity.schema_validations()
|
||||
child_validated = True
|
||||
break
|
||||
|
||||
if not child_validated:
|
||||
key = "__tmp__"
|
||||
tmp_child = self._add_key(key)
|
||||
tmp_child.schema_validations()
|
||||
self.children_by_key.pop(key)
|
||||
|
||||
def get_child_path(self, child_obj):
|
||||
result_key = None
|
||||
|
|
@ -522,7 +532,7 @@ class DictMutableKeysEntity(EndpointEntity):
|
|||
self.had_project_override = value is not NOT_SET
|
||||
|
||||
def _discard_changes(self, on_change_trigger):
|
||||
if not self.can_discard_changes:
|
||||
if not self._can_discard_changes:
|
||||
return
|
||||
|
||||
self.set_override_state(self._override_state)
|
||||
|
|
@ -533,7 +543,7 @@ class DictMutableKeysEntity(EndpointEntity):
|
|||
self.on_change()
|
||||
|
||||
def _remove_from_studio_default(self, on_change_trigger):
|
||||
if not self.can_remove_from_studio_default:
|
||||
if not self._can_remove_from_studio_default:
|
||||
return
|
||||
|
||||
value = self._default_value
|
||||
|
|
@ -574,7 +584,7 @@ class DictMutableKeysEntity(EndpointEntity):
|
|||
self.on_change()
|
||||
|
||||
def _remove_from_project_override(self, on_change_trigger):
|
||||
if not self.can_remove_from_project_override:
|
||||
if not self._can_remove_from_project_override:
|
||||
return
|
||||
|
||||
if self._has_studio_override:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class EndpointEntity(ItemEntity):
|
|||
super(EndpointEntity, self).__init__(*args, **kwargs)
|
||||
|
||||
if (
|
||||
not (self.group_item or self.is_group)
|
||||
not (self.group_item is not None or self.is_group)
|
||||
and not (self.is_dynamic_item or self.is_in_dynamic_item)
|
||||
):
|
||||
self.is_group = True
|
||||
|
|
@ -251,7 +251,7 @@ class InputEntity(EndpointEntity):
|
|||
self._current_value = copy.deepcopy(value)
|
||||
|
||||
def _discard_changes(self, on_change_trigger=None):
|
||||
if not self.can_discard_changes:
|
||||
if not self._can_discard_changes:
|
||||
return
|
||||
|
||||
self._value_is_modified = False
|
||||
|
|
@ -289,7 +289,7 @@ class InputEntity(EndpointEntity):
|
|||
self.on_change()
|
||||
|
||||
def _remove_from_studio_default(self, on_change_trigger):
|
||||
if not self.can_remove_from_studio_default:
|
||||
if not self._can_remove_from_studio_default:
|
||||
return
|
||||
|
||||
value = self._default_value
|
||||
|
|
@ -307,7 +307,7 @@ class InputEntity(EndpointEntity):
|
|||
self.on_change()
|
||||
|
||||
def _remove_from_project_override(self, on_change_trigger):
|
||||
if not self.can_remove_from_project_override:
|
||||
if not self._can_remove_from_project_override:
|
||||
return
|
||||
|
||||
self._has_project_override = False
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class PathEntity(ItemEntity):
|
|||
return self.child_obj.items()
|
||||
|
||||
def _item_initalization(self):
|
||||
if not self.group_item and not self.is_group:
|
||||
if self.group_item is None and not self.is_group:
|
||||
self.is_group = True
|
||||
|
||||
self.multiplatform = self.schema_data.get("multiplatform", False)
|
||||
|
|
@ -199,7 +199,7 @@ class ListStrictEntity(ItemEntity):
|
|||
|
||||
# GUI attribute
|
||||
self.is_horizontal = self.schema_data.get("horizontal", True)
|
||||
if not self.group_item and not self.is_group:
|
||||
if self.group_item is None and not self.is_group:
|
||||
self.is_group = True
|
||||
|
||||
def schema_validations(self):
|
||||
|
|
@ -453,4 +453,5 @@ class ListStrictEntity(ItemEntity):
|
|||
|
||||
def reset_callbacks(self):
|
||||
super(ListStrictEntity, self).reset_callbacks()
|
||||
self.child_obj.reset_callbacks()
|
||||
for child_obj in self.children:
|
||||
child_obj.reset_callbacks()
|
||||
|
|
|
|||
|
|
@ -59,8 +59,7 @@ class ListEntity(EndpointEntity):
|
|||
)
|
||||
|
||||
def append(self, item):
|
||||
child_obj = self._add_new_item()
|
||||
child_obj.set_override_state(self._override_state)
|
||||
child_obj = self.add_new_item(trigger_change=False)
|
||||
child_obj.set(item)
|
||||
self.on_change()
|
||||
|
||||
|
|
@ -92,8 +91,7 @@ class ListEntity(EndpointEntity):
|
|||
raise ValueError("ListEntity.remove(x): x not in ListEntity")
|
||||
|
||||
def insert(self, idx, item):
|
||||
child_obj = self._add_new_item(idx)
|
||||
child_obj.set_override_state(self._override_state)
|
||||
child_obj = self.add_new_item(idx, trigger_change=False)
|
||||
child_obj.set(item)
|
||||
self.on_change()
|
||||
|
||||
|
|
@ -105,10 +103,16 @@ class ListEntity(EndpointEntity):
|
|||
self.children.insert(idx, child_obj)
|
||||
return child_obj
|
||||
|
||||
def add_new_item(self, idx=None):
|
||||
def add_new_item(self, idx=None, trigger_change=True):
|
||||
child_obj = self._add_new_item(idx)
|
||||
child_obj.set_override_state(self._override_state)
|
||||
self.on_change()
|
||||
if self._override_state is OverrideState.STUDIO:
|
||||
child_obj.add_to_studio_default([])
|
||||
elif self._override_state is OverrideState.PROJECT:
|
||||
child_obj.add_to_project_default([])
|
||||
|
||||
if trigger_change:
|
||||
self.on_change()
|
||||
return child_obj
|
||||
|
||||
def swap_items(self, item_1, item_2):
|
||||
|
|
@ -144,7 +148,7 @@ class ListEntity(EndpointEntity):
|
|||
item_schema = {"type": item_schema}
|
||||
self.item_schema = item_schema
|
||||
|
||||
if not self.group_item:
|
||||
if self.group_item is None:
|
||||
self.is_group = True
|
||||
|
||||
# Value that was set on set_override_state
|
||||
|
|
@ -167,8 +171,18 @@ class ListEntity(EndpointEntity):
|
|||
)
|
||||
raise EntitySchemaError(self, reason)
|
||||
|
||||
for child_obj in self.children:
|
||||
child_obj.schema_validations()
|
||||
# Validate object type schema
|
||||
child_validated = False
|
||||
for child_entity in self.children:
|
||||
child_entity.schema_validations()
|
||||
child_validated = True
|
||||
break
|
||||
|
||||
if not child_validated:
|
||||
idx = 0
|
||||
tmp_child = self._add_new_item(idx)
|
||||
tmp_child.schema_validations()
|
||||
self.children.pop(idx)
|
||||
|
||||
def get_child_path(self, child_obj):
|
||||
result_idx = None
|
||||
|
|
@ -343,7 +357,7 @@ class ListEntity(EndpointEntity):
|
|||
return output
|
||||
|
||||
def _discard_changes(self, on_change_trigger):
|
||||
if not self.can_discard_changes:
|
||||
if not self._can_discard_changes:
|
||||
return
|
||||
|
||||
not_set = object()
|
||||
|
|
@ -405,7 +419,7 @@ class ListEntity(EndpointEntity):
|
|||
self.on_change()
|
||||
|
||||
def _remove_from_studio_default(self, on_change_trigger):
|
||||
if not self.can_remove_from_studio_default:
|
||||
if not self._can_remove_from_studio_default:
|
||||
return
|
||||
|
||||
value = self._default_value
|
||||
|
|
@ -433,7 +447,7 @@ class ListEntity(EndpointEntity):
|
|||
self.on_change()
|
||||
|
||||
def _remove_from_project_override(self, on_change_trigger):
|
||||
if not self.can_remove_from_project_override:
|
||||
if not self._can_remove_from_project_override:
|
||||
return
|
||||
|
||||
if self._has_studio_override:
|
||||
|
|
|
|||
|
|
@ -247,8 +247,7 @@
|
|||
"label": "Used in plugins",
|
||||
"object_type": {
|
||||
"type": "text",
|
||||
"key": "pluginClass",
|
||||
"label": "Plugin Class"
|
||||
"key": "pluginClass"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -295,8 +294,7 @@
|
|||
"label": "Used in plugins",
|
||||
"object_type": {
|
||||
"type": "text",
|
||||
"key": "pluginClass",
|
||||
"label": "Plugin Class"
|
||||
"key": "pluginClass"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -301,20 +301,24 @@
|
|||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "opacity",
|
||||
"label": "Font opacity",
|
||||
"decimal": 2,
|
||||
"maximum": 1,
|
||||
"minimum": 0
|
||||
"type": "schema_template",
|
||||
"name": "template_rgba_color",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Font Color",
|
||||
"name": "font_color"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "bg_opacity",
|
||||
"label": "Background opacity",
|
||||
"decimal": 2,
|
||||
"maximum": 1,
|
||||
"minimum": 0
|
||||
"type": "schema_template",
|
||||
"name": "template_rgba_color",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Background Color",
|
||||
"name": "bg_color"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
"key": "filters",
|
||||
"label": "Publish GUI Filters",
|
||||
"object_type": {
|
||||
"type": "raw-json",
|
||||
"label": "Plugins"
|
||||
"type": "raw-json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
"object_type": {
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,18 @@
|
|||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "This is <b>NOT a securely stored password!</b>. It only acts as a simple barrier to stop users from accessing studio wide settings."
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "admin_password",
|
||||
"label": "Admin password"
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
},
|
||||
{
|
||||
"key": "environment",
|
||||
"label": "Environment",
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ class CacheValues:
|
|||
|
||||
class MongoSettingsHandler(SettingsHandler):
|
||||
"""Settings handler that use mongo for storing and loading of settings."""
|
||||
global_general_keys = ("openpype_path", )
|
||||
global_general_keys = ("openpype_path", "admin_password")
|
||||
|
||||
def __init__(self):
|
||||
# Get mongo connection
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import sys
|
||||
from Qt import QtWidgets, QtGui
|
||||
from .lib import is_password_required
|
||||
from .widgets import PasswordDialog
|
||||
from .local_settings import LocalSettingsWindow
|
||||
from .settings import (
|
||||
style,
|
||||
|
|
@ -24,13 +26,14 @@ def main(user_role=None):
|
|||
|
||||
widget = MainWidget(user_role)
|
||||
widget.show()
|
||||
widget.reset()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
__all__ = (
|
||||
"is_password_required",
|
||||
"style",
|
||||
"PasswordDialog",
|
||||
"MainWidget",
|
||||
"ProjectListWidget",
|
||||
"LocalSettingsWindow",
|
||||
|
|
|
|||
16
openpype/tools/settings/lib.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
def is_password_required():
|
||||
from openpype.settings import (
|
||||
get_system_settings,
|
||||
get_local_settings
|
||||
)
|
||||
|
||||
system_settings = get_system_settings()
|
||||
password = system_settings["general"].get("admin_password")
|
||||
if not password:
|
||||
return False
|
||||
|
||||
local_settings = get_local_settings()
|
||||
is_admin = local_settings.get("general", {}).get("is_admin", False)
|
||||
if is_admin:
|
||||
return False
|
||||
return True
|
||||
|
|
@ -1,28 +1,77 @@
|
|||
import getpass
|
||||
|
||||
from Qt import QtWidgets
|
||||
from Qt import QtWidgets, QtCore
|
||||
from openpype.tools.settings import (
|
||||
is_password_required,
|
||||
PasswordDialog
|
||||
)
|
||||
|
||||
|
||||
class LocalGeneralWidgets(QtWidgets.QWidget):
|
||||
def __init__(self, parent):
|
||||
super(LocalGeneralWidgets, self).__init__(parent)
|
||||
|
||||
self._loading_local_settings = False
|
||||
|
||||
username_input = QtWidgets.QLineEdit(self)
|
||||
username_input.setPlaceholderText(getpass.getuser())
|
||||
|
||||
is_admin_input = QtWidgets.QCheckBox(self)
|
||||
|
||||
layout = QtWidgets.QFormLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
layout.addRow("OpenPype Username", username_input)
|
||||
layout.addRow("Admin permissions", is_admin_input)
|
||||
|
||||
is_admin_input.stateChanged.connect(self._on_admin_check_change)
|
||||
|
||||
self.username_input = username_input
|
||||
self.is_admin_input = is_admin_input
|
||||
|
||||
def update_local_settings(self, value):
|
||||
self._loading_local_settings = True
|
||||
|
||||
username = ""
|
||||
is_admin = False
|
||||
if value:
|
||||
username = value.get("username", username)
|
||||
is_admin = value.get("is_admin", is_admin)
|
||||
|
||||
self.username_input.setText(username)
|
||||
|
||||
if self.is_admin_input.isChecked() != is_admin:
|
||||
# Use state as `stateChanged` is connected to callbacks
|
||||
if is_admin:
|
||||
state = QtCore.Qt.Checked
|
||||
else:
|
||||
state = QtCore.Qt.Unchecked
|
||||
self.is_admin_input.setCheckState(state)
|
||||
|
||||
self._loading_local_settings = False
|
||||
|
||||
def _on_admin_check_change(self):
|
||||
if self._loading_local_settings:
|
||||
return
|
||||
|
||||
if not self.is_admin_input.isChecked():
|
||||
return
|
||||
|
||||
if not is_password_required():
|
||||
return
|
||||
|
||||
dialog = PasswordDialog(self, False)
|
||||
dialog.setModal(True)
|
||||
dialog.exec_()
|
||||
result = dialog.result()
|
||||
if self.is_admin_input.isChecked() != result:
|
||||
# Use state as `stateChanged` is connected to callbacks
|
||||
if result:
|
||||
state = QtCore.Qt.Checked
|
||||
else:
|
||||
state = QtCore.Qt.Unchecked
|
||||
self.is_admin_input.setCheckState(state)
|
||||
|
||||
def settings_value(self):
|
||||
# Add changed
|
||||
# If these have changed then
|
||||
|
|
@ -30,6 +79,8 @@ class LocalGeneralWidgets(QtWidgets.QWidget):
|
|||
username = self.username_input.text()
|
||||
if username:
|
||||
output["username"] = username
|
||||
# Do not return output yet since we don't have mechanism to save or
|
||||
# load these data through api calls
|
||||
|
||||
is_admin = self.is_admin_input.isChecked()
|
||||
if is_admin:
|
||||
output["is_admin"] = is_admin
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ class LocalSettingsWindow(QtWidgets.QWidget):
|
|||
def __init__(self, parent=None):
|
||||
super(LocalSettingsWindow, self).__init__(parent)
|
||||
|
||||
self._reset_on_show = True
|
||||
|
||||
self.resize(1000, 600)
|
||||
|
||||
self.setWindowTitle("OpenPype Local settings")
|
||||
|
|
@ -193,9 +195,14 @@ class LocalSettingsWindow(QtWidgets.QWidget):
|
|||
self.reset_btn = reset_btn
|
||||
self.save_btn = save_btn
|
||||
|
||||
self.reset()
|
||||
def showEvent(self, event):
|
||||
super(LocalSettingsWindow, self).showEvent(event)
|
||||
if self._reset_on_show:
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
if self._reset_on_show:
|
||||
self._reset_on_show = False
|
||||
value = get_local_settings()
|
||||
self.settings_widget.update_local_settings(value)
|
||||
|
||||
|
|
|
|||
8
openpype/tools/settings/resources/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import os
|
||||
|
||||
|
||||
RESOURCES_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def get_resource(*args):
|
||||
return os.path.normpath(os.path.join(RESOURCES_DIR, *args))
|
||||
BIN
openpype/tools/settings/resources/images/eye.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -71,7 +71,7 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
def _discard_changes_action(self, menu, actions_mapping):
|
||||
# TODO use better condition as unsaved changes may be caused due to
|
||||
# changes in schema.
|
||||
if not self.entity.can_discard_changes:
|
||||
if not self.entity.can_trigger_discard_changes:
|
||||
return
|
||||
|
||||
def discard_changes():
|
||||
|
|
@ -86,7 +86,7 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
def _add_to_studio_default(self, menu, actions_mapping):
|
||||
"""Set values as studio overrides."""
|
||||
# Skip if not in studio overrides
|
||||
if not self.entity.can_add_to_studio_default:
|
||||
if not self.entity.can_trigger_add_to_studio_default:
|
||||
return
|
||||
|
||||
action = QtWidgets.QAction("Add to studio default")
|
||||
|
|
@ -94,7 +94,7 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
menu.addAction(action)
|
||||
|
||||
def _remove_from_studio_default_action(self, menu, actions_mapping):
|
||||
if not self.entity.can_remove_from_studio_default:
|
||||
if not self.entity.can_trigger_remove_from_studio_default:
|
||||
return
|
||||
|
||||
def remove_from_studio_default():
|
||||
|
|
@ -106,7 +106,7 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
menu.addAction(action)
|
||||
|
||||
def _add_to_project_override_action(self, menu, actions_mapping):
|
||||
if not self.entity.can_add_to_project_override:
|
||||
if not self.entity.can_trigger_add_to_project_override:
|
||||
return
|
||||
|
||||
action = QtWidgets.QAction("Add to project project override")
|
||||
|
|
@ -114,7 +114,7 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
menu.addAction(action)
|
||||
|
||||
def _remove_from_project_override_action(self, menu, actions_mapping):
|
||||
if not self.entity.can_remove_from_project_override:
|
||||
if not self.entity.can_trigger_remove_from_project_override:
|
||||
return
|
||||
|
||||
def remove_from_project_override():
|
||||
|
|
|
|||
|
|
@ -318,9 +318,14 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
"`create_root_entity` method not implemented"
|
||||
)
|
||||
|
||||
def _on_reset_start(self):
|
||||
return
|
||||
|
||||
def reset(self):
|
||||
self.set_state(CategoryState.Working)
|
||||
|
||||
self._on_reset_start()
|
||||
|
||||
self.input_fields = []
|
||||
|
||||
while self.content_layout.count() != 0:
|
||||
|
|
@ -485,7 +490,6 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
|
||||
def ui_tweaks(self):
|
||||
project_list_widget = ProjectListWidget(self)
|
||||
project_list_widget.refresh()
|
||||
|
||||
self.main_layout.insertWidget(0, project_list_widget, 0)
|
||||
|
||||
|
|
@ -501,6 +505,9 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
if self is saved_tab_widget:
|
||||
return
|
||||
|
||||
def _on_reset_start(self):
|
||||
self.project_list_widget.refresh()
|
||||
|
||||
def _on_reset_crash(self):
|
||||
self.project_list_widget.setEnabled(False)
|
||||
super(ProjectWidget, self)._on_reset_crash()
|
||||
|
|
|
|||
|
|
@ -661,8 +661,14 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
self.current_project = None
|
||||
|
||||
if self.dbcon:
|
||||
for project_name in self.dbcon.database.collection_names():
|
||||
items.append(project_name)
|
||||
database = self.dbcon.database
|
||||
for project_name in database.collection_names():
|
||||
project_doc = database[project_name].find_one(
|
||||
{"type": "project"},
|
||||
{"name": 1}
|
||||
)
|
||||
if project_doc:
|
||||
items.append(project_doc["name"])
|
||||
for item in items:
|
||||
model.appendRow(QtGui.QStandardItem(item))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets, QtGui
|
||||
from Qt import QtWidgets, QtGui, QtCore
|
||||
from .categories import (
|
||||
CategoryState,
|
||||
SystemWidget,
|
||||
|
|
@ -7,6 +7,11 @@ from .categories import (
|
|||
from .widgets import ShadowWidget
|
||||
from .. import style
|
||||
|
||||
from openpype.tools.settings import (
|
||||
is_password_required,
|
||||
PasswordDialog
|
||||
)
|
||||
|
||||
|
||||
class MainWidget(QtWidgets.QWidget):
|
||||
widget_width = 1000
|
||||
|
|
@ -14,6 +19,12 @@ class MainWidget(QtWidgets.QWidget):
|
|||
|
||||
def __init__(self, user_role, parent=None):
|
||||
super(MainWidget, self).__init__(parent)
|
||||
|
||||
self._user_passed = False
|
||||
self._reset_on_show = True
|
||||
|
||||
self._password_dialog = None
|
||||
|
||||
self.setObjectName("MainWidget")
|
||||
self.setWindowTitle("OpenPype Settings")
|
||||
|
||||
|
|
@ -44,6 +55,7 @@ class MainWidget(QtWidgets.QWidget):
|
|||
self.setLayout(layout)
|
||||
|
||||
self._shadow_widget = ShadowWidget("Working...", self)
|
||||
self._shadow_widget.setVisible(False)
|
||||
|
||||
for tab_widget in tab_widgets:
|
||||
tab_widget.saved.connect(self._on_tab_save)
|
||||
|
|
@ -75,6 +87,48 @@ class MainWidget(QtWidgets.QWidget):
|
|||
if app:
|
||||
app.processEvents()
|
||||
|
||||
def showEvent(self, event):
|
||||
super(MainWidget, self).showEvent(event)
|
||||
if self._reset_on_show:
|
||||
self.reset()
|
||||
|
||||
def _show_password_dialog(self):
|
||||
if self._password_dialog:
|
||||
self._password_dialog.open()
|
||||
|
||||
def _on_password_dialog_close(self, password_passed):
|
||||
# Store result for future settings reset
|
||||
self._user_passed = password_passed
|
||||
# Remove reference to password dialog
|
||||
self._password_dialog = None
|
||||
if password_passed:
|
||||
self.reset()
|
||||
else:
|
||||
self.close()
|
||||
|
||||
def reset(self):
|
||||
if self._password_dialog:
|
||||
return
|
||||
|
||||
if not self._user_passed:
|
||||
self._user_passed = not is_password_required()
|
||||
|
||||
self._on_state_change()
|
||||
|
||||
if not self._user_passed:
|
||||
# Avoid doubled dialog
|
||||
dialog = PasswordDialog(self)
|
||||
dialog.setModal(True)
|
||||
dialog.finished.connect(self._on_password_dialog_close)
|
||||
|
||||
self._password_dialog = dialog
|
||||
|
||||
QtCore.QTimer.singleShot(100, self._show_password_dialog)
|
||||
|
||||
return
|
||||
|
||||
if self._reset_on_show:
|
||||
self._reset_on_show = False
|
||||
|
||||
for tab_widget in self.tab_widgets:
|
||||
tab_widget.reset()
|
||||
|
|
|
|||
164
openpype/tools/settings/widgets.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from .resources import get_resource
|
||||
|
||||
from openpype.api import get_system_settings
|
||||
from openpype.settings.lib import (
|
||||
get_local_settings,
|
||||
save_local_settings
|
||||
)
|
||||
|
||||
|
||||
class PressHoverButton(QtWidgets.QPushButton):
|
||||
_mouse_pressed = False
|
||||
_mouse_hovered = False
|
||||
change_state = QtCore.Signal(bool)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self._mouse_pressed = True
|
||||
self._mouse_hovered = True
|
||||
self.change_state.emit(self._mouse_hovered)
|
||||
super(PressHoverButton, self).mousePressEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
self._mouse_pressed = False
|
||||
self._mouse_hovered = False
|
||||
self.change_state.emit(self._mouse_hovered)
|
||||
super(PressHoverButton, self).mouseReleaseEvent(event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
mouse_pos = self.mapFromGlobal(QtGui.QCursor.pos())
|
||||
under_mouse = self.rect().contains(mouse_pos)
|
||||
if under_mouse != self._mouse_hovered:
|
||||
self._mouse_hovered = under_mouse
|
||||
self.change_state.emit(self._mouse_hovered)
|
||||
|
||||
super(PressHoverButton, self).mouseMoveEvent(event)
|
||||
|
||||
|
||||
class PasswordDialog(QtWidgets.QDialog):
|
||||
"""Stupidly simple dialog to compare password from general settings."""
|
||||
finished = QtCore.Signal(bool)
|
||||
|
||||
def __init__(self, parent=None, allow_remember=True):
|
||||
super(PasswordDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("Settings Password")
|
||||
self.resize(300, 120)
|
||||
|
||||
system_settings = get_system_settings()
|
||||
|
||||
self._expected_result = (
|
||||
system_settings["general"].get("admin_password")
|
||||
)
|
||||
self._final_result = None
|
||||
self._allow_remember = allow_remember
|
||||
|
||||
# Password input
|
||||
password_widget = QtWidgets.QWidget(self)
|
||||
|
||||
password_label = QtWidgets.QLabel("Password:", password_widget)
|
||||
|
||||
password_input = QtWidgets.QLineEdit(password_widget)
|
||||
password_input.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||
|
||||
show_password_icon_path = get_resource("images", "eye.png")
|
||||
show_password_icon = QtGui.QIcon(show_password_icon_path)
|
||||
show_password_btn = PressHoverButton(password_widget)
|
||||
show_password_btn.setIcon(show_password_icon)
|
||||
show_password_btn.setStyleSheet((
|
||||
"border: none;padding:0.1em;"
|
||||
))
|
||||
show_password_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
|
||||
|
||||
password_layout = QtWidgets.QHBoxLayout(password_widget)
|
||||
password_layout.setContentsMargins(0, 0, 0, 0)
|
||||
password_layout.addWidget(password_label)
|
||||
password_layout.addWidget(password_input)
|
||||
password_layout.addWidget(show_password_btn)
|
||||
|
||||
message_label = QtWidgets.QLabel("", self)
|
||||
|
||||
# Buttons
|
||||
buttons_widget = QtWidgets.QWidget(self)
|
||||
|
||||
remember_checkbox = QtWidgets.QCheckBox("Remember", buttons_widget)
|
||||
remember_checkbox.setVisible(allow_remember)
|
||||
remember_checkbox.setStyleSheet((
|
||||
"spacing: 0.5em;"
|
||||
))
|
||||
|
||||
ok_btn = QtWidgets.QPushButton("Ok", buttons_widget)
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel", buttons_widget)
|
||||
|
||||
buttons_layout = QtWidgets.QHBoxLayout(buttons_widget)
|
||||
buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||
buttons_layout.addWidget(remember_checkbox)
|
||||
buttons_layout.addStretch(1)
|
||||
buttons_layout.addWidget(ok_btn)
|
||||
buttons_layout.addWidget(cancel_btn)
|
||||
|
||||
# Main layout
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addSpacing(10)
|
||||
layout.addWidget(password_widget, 0)
|
||||
layout.addWidget(message_label, 0)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(buttons_widget, 0)
|
||||
|
||||
ok_btn.clicked.connect(self._on_ok_click)
|
||||
cancel_btn.clicked.connect(self._on_cancel_click)
|
||||
show_password_btn.change_state.connect(self._on_show_password)
|
||||
|
||||
self.password_input = password_input
|
||||
self.remember_checkbox = remember_checkbox
|
||||
self.message_label = message_label
|
||||
|
||||
def remember_password(self):
|
||||
if not self._allow_remember:
|
||||
return False
|
||||
return self.remember_checkbox.isChecked()
|
||||
|
||||
def result(self):
|
||||
if self._final_result is None:
|
||||
return False
|
||||
return self._final_result == self._expected_result
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
||||
self._on_ok_click()
|
||||
return event.accept()
|
||||
super(PasswordDialog, self).keyPressEvent(event)
|
||||
|
||||
def closeEvent(self, event):
|
||||
super(PasswordDialog, self).closeEvent(event)
|
||||
self.finished.emit(self.result())
|
||||
|
||||
def _on_ok_click(self):
|
||||
input_value = self.password_input.text()
|
||||
if input_value != self._expected_result:
|
||||
self.message_label.setText("Invalid password. Try it again...")
|
||||
self.password_input.setFocus()
|
||||
return
|
||||
|
||||
if self.remember_password():
|
||||
local_settings = get_local_settings()
|
||||
if "general" not in local_settings:
|
||||
local_settings["general"] = {}
|
||||
|
||||
local_settings["general"]["is_admin"] = True
|
||||
|
||||
save_local_settings(local_settings)
|
||||
|
||||
self._final_result = input_value
|
||||
self.close()
|
||||
|
||||
def _on_show_password(self, show_password):
|
||||
if show_password:
|
||||
echo_mode = QtWidgets.QLineEdit.Normal
|
||||
else:
|
||||
echo_mode = QtWidgets.QLineEdit.Password
|
||||
self.password_input.setEchoMode(echo_mode)
|
||||
|
||||
def _on_cancel_click(self):
|
||||
self.close()
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.0.0-beta2"
|
||||
__version__ = "3.0.0-rc2"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.0.0-beta2"
|
||||
version = "3.0.0-rc2"
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 807e8577a0268580a2934ba38889911adad26eb1
|
||||
Subproject commit cfd4191e364b47de7364096f45d9d9d9a901692a
|
||||
|
|
@ -70,8 +70,6 @@ function Install-Poetry() {
|
|||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Installing Poetry ... "
|
||||
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python -
|
||||
# add it to PATH
|
||||
$env:PATH = "$($env:PATH);$($env:USERPROFILE)\.poetry\bin"
|
||||
}
|
||||
|
||||
$art = @"
|
||||
|
|
@ -101,6 +99,14 @@ $current_dir = Get-Location
|
|||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
$version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
|
||||
|
|
@ -134,47 +140,20 @@ Write-Host "Making sure submodules are up-to-date ..."
|
|||
git submodule update --init --recursive
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Building OpenPype [ " -NoNewline -ForegroundColor white
|
||||
Write-Host "OpenPype [ " -NoNewline -ForegroundColor white
|
||||
Write-host $openpype_version -NoNewline -ForegroundColor green
|
||||
Write-Host " ] ..." -ForegroundColor white
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Detecting host Python ... " -NoNewline
|
||||
if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "!!! Python not detected" -ForegroundColor red
|
||||
Exit-WithCode 1
|
||||
}
|
||||
$version_command = @"
|
||||
import sys
|
||||
print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))
|
||||
"@
|
||||
|
||||
$p = & python -c $version_command
|
||||
$env:PYTHON_VERSION = $p
|
||||
$m = $p -match '(\d+)\.(\d+)'
|
||||
if(-not $m) {
|
||||
Write-Host "!!! Cannot determine version" -ForegroundColor red
|
||||
Exit-WithCode 1
|
||||
}
|
||||
# We are supporting python 3.6 and up
|
||||
if(($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
|
||||
Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red
|
||||
Exit-WithCode 1
|
||||
}
|
||||
Write-Host "OK [ $p ]" -ForegroundColor green
|
||||
|
||||
Write-Host " ]" -ForegroundColor white
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$($env:USERPROFILE)\.poetry\bin")) {
|
||||
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Install-Poetry
|
||||
|
||||
Write-Host "INSTALLED" -ForegroundColor Cyan
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:USERPROFILE)\.poetry\bin"
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Cleaning cache files ... " -NoNewline
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ install_poetry () {
|
|||
echo -e "${BIGreen}>>>${RST} Installing Poetry ..."
|
||||
command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; }
|
||||
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3 -
|
||||
export PATH="$PATH:$HOME/.poetry/bin"
|
||||
}
|
||||
|
||||
# Main
|
||||
|
|
@ -149,6 +148,14 @@ main () {
|
|||
version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);"
|
||||
openpype_version="$(python3 <<< ${version_command})"
|
||||
|
||||
_inside_openpype_tool="1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if [[ -z $POETRY_HOME ]]; then
|
||||
export POETRY_HOME="$openpype_root/.poetry"
|
||||
fi
|
||||
export PATH="$POETRY_HOME/bin:$PATH"
|
||||
|
||||
echo -e "${BIYellow}---${RST} Cleaning build directory ..."
|
||||
rm -rf "$openpype_root/build" && mkdir "$openpype_root/build" > /dev/null
|
||||
|
||||
|
|
@ -157,12 +164,12 @@ main () {
|
|||
clean_pyc
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
|
||||
if [ -f "$HOME/.poetry/bin/poetry" ]; then
|
||||
if [ -f "$POETRY_HOME/bin/poetry" ]; then
|
||||
echo -e "${BIGreen}OK${RST}"
|
||||
export PATH="$PATH:$HOME/.poetry/bin"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
fi
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Making sure submodules are up-to-date ..."
|
||||
|
|
|
|||
|
|
@ -43,9 +43,10 @@ function Show-PSWarning() {
|
|||
function Install-Poetry() {
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Installing Poetry ... "
|
||||
$env:POETRY_HOME="$openpype_root\.poetry"
|
||||
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python -
|
||||
# add it to PATH
|
||||
$env:PATH = "$($env:PATH);$($env:USERPROFILE)\.poetry\bin"
|
||||
$env:PATH = "$($env:PATH);$openpype_root\.poetry\bin"
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -84,6 +85,12 @@ $current_dir = Get-Location
|
|||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
$art = @"
|
||||
|
|
@ -105,8 +112,9 @@ $art = @"
|
|||
|
||||
|
||||
"@
|
||||
|
||||
Write-Host $art -ForegroundColor DarkGreen
|
||||
if (-not (Test-Path 'env:_INSIDE_OPENPYPE_TOOL')) {
|
||||
Write-Host $art -ForegroundColor DarkGreen
|
||||
}
|
||||
|
||||
# Enable if PS 7.x is needed.
|
||||
# Show-PSWarning
|
||||
|
|
@ -128,7 +136,7 @@ Test-Python
|
|||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$($env:USERPROFILE)\.poetry\bin")) {
|
||||
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Install-Poetry
|
||||
Write-Host "INSTALLED" -ForegroundColor Cyan
|
||||
|
|
|
|||
|
|
@ -144,19 +144,27 @@ realpath () {
|
|||
|
||||
main () {
|
||||
# Main
|
||||
echo -e "${BGreen}"
|
||||
art
|
||||
echo -e "${RST}"
|
||||
if [[ -z $_inside_openpype_tool ]]; then
|
||||
echo -e "${BGreen}"
|
||||
art
|
||||
echo -e "${RST}"
|
||||
fi
|
||||
detect_python || return 1
|
||||
|
||||
# Directories
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
# make sure Poetry is in PATH
|
||||
if [[ -z $POETRY_HOME ]]; then
|
||||
export POETRY_HOME="$openpype_root/.poetry"
|
||||
fi
|
||||
export PATH="$POETRY_HOME/bin:$PATH"
|
||||
|
||||
|
||||
pushd "$openpype_root" > /dev/null || return > /dev/null
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
|
||||
if [ -f "$HOME/.poetry/bin/poetry" ]; then
|
||||
if [ -f "$POETRY_HOME/bin/poetry" ]; then
|
||||
echo -e "${BIGreen}OK${RST}"
|
||||
export PATH="$PATH:$HOME/.poetry/bin"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
|
|
|
|||
|
|
@ -37,6 +37,15 @@ function Show-PSWarning() {
|
|||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
$art = @"
|
||||
|
|
@ -71,6 +80,17 @@ if (-not $openpype_version) {
|
|||
Exit-WithCode 1
|
||||
}
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Cleaning cache files ... " -NoNewline
|
||||
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Remove-Item -Force
|
||||
|
|
|
|||
|
|
@ -128,8 +128,26 @@ main () {
|
|||
|
||||
# Directories
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
||||
_inside_openpype_tool="1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if [[ -z $POETRY_HOME ]]; then
|
||||
export POETRY_HOME="$openpype_root/.poetry"
|
||||
fi
|
||||
export PATH="$POETRY_HOME/bin:$PATH"
|
||||
|
||||
pushd "$openpype_root" > /dev/null || return > /dev/null
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
|
||||
if [ -f "$POETRY_HOME/bin/poetry" ]; then
|
||||
echo -e "${BIGreen}OK${RST}"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
fi
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..."
|
||||
PYTHONPATH="$openpype_root:$PYTHONPATH"
|
||||
OPENPYPE_ROOT="$openpype_root"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,28 @@ PS> .\fetch_thirdparty_libs.ps1
|
|||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
|
||||
& poetry run python "$($openpype_root)\tools\fetch_thirdparty_libs.py"
|
||||
Set-Location -Path $current_dir
|
||||
|
|
|
|||
|
|
@ -116,14 +116,31 @@ main () {
|
|||
echo -e "${BGreen}"
|
||||
art
|
||||
echo -e "${RST}"
|
||||
detect_python || return 1
|
||||
|
||||
# Directories
|
||||
pype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
pushd "$pype_root" > /dev/null || return > /dev/null
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
||||
_inside_openpype_tool="1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if [[ -z $POETRY_HOME ]]; then
|
||||
export POETRY_HOME="$openpype_root/.poetry"
|
||||
fi
|
||||
export PATH="$POETRY_HOME/bin:$PATH"
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
|
||||
if [ -f "$POETRY_HOME/bin/poetry" ]; then
|
||||
echo -e "${BIGreen}OK${RST}"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
fi
|
||||
|
||||
pushd "$openpype_root" > /dev/null || return > /dev/null
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Running Pype tool ..."
|
||||
poetry run python3 "$pype_root/tools/fetch_thirdparty_libs.py"
|
||||
poetry run python3 "$openpype_root/tools/fetch_thirdparty_libs.py"
|
||||
}
|
||||
|
||||
main
|
||||
|
|
@ -16,6 +16,15 @@ PS> .\make_docs.ps1
|
|||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
|
||||
|
|
@ -39,6 +48,17 @@ $art = @"
|
|||
|
||||
Write-Host $art -ForegroundColor DarkGreen
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host "This will not overwrite existing source rst files, only scan and add new."
|
||||
Set-Location -Path $openpype_root
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
|
|
|
|||
|
|
@ -80,6 +80,24 @@ main () {
|
|||
|
||||
# Directories
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
||||
_inside_openpype_tool="1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if [[ -z $POETRY_HOME ]]; then
|
||||
export POETRY_HOME="$openpype_root/.poetry"
|
||||
fi
|
||||
export PATH="$POETRY_HOME/bin:$PATH"
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
|
||||
if [ -f "$POETRY_HOME/bin/poetry" ]; then
|
||||
echo -e "${BIGreen}OK${RST}"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
fi
|
||||
|
||||
pushd "$openpype_root" > /dev/null || return > /dev/null
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Running apidoc ..."
|
||||
|
|
|
|||
32
tools/run_documentation.ps1
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Helper script to run mongodb.
|
||||
|
||||
.DESCRIPTION
|
||||
This script will detect mongodb, add it to the PATH and launch it on specified port and db location.
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
PS> .\run_mongo.ps1
|
||||
|
||||
#>
|
||||
|
||||
$art = @"
|
||||
|
||||
▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀
|
||||
▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░
|
||||
▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄
|
||||
.---= [ by Pype Club ] =---.
|
||||
https://openpype.io
|
||||
|
||||
"@
|
||||
|
||||
Write-Host $art -ForegroundColor DarkGreen
|
||||
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
cd $openpype_root/website
|
||||
|
||||
yarn run start
|
||||
|
||||
|
|
@ -14,6 +14,27 @@ PS> .\run_settings.ps1
|
|||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
|
||||
& poetry run python "$($openpype_root)\start.py" settings --dev
|
||||
Set-Location -Path $current_dir
|
||||
|
|
@ -57,6 +57,7 @@ BIPurple='\033[1;95m' # Purple
|
|||
BICyan='\033[1;96m' # Cyan
|
||||
BIWhite='\033[1;97m' # White
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Return absolute path
|
||||
# Globals:
|
||||
|
|
@ -72,10 +73,29 @@ realpath () {
|
|||
|
||||
# Main
|
||||
main () {
|
||||
|
||||
# Directories
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
||||
_inside_openpype_tool="1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if [[ -z $POETRY_HOME ]]; then
|
||||
export POETRY_HOME="$openpype_root/.poetry"
|
||||
fi
|
||||
export PATH="$POETRY_HOME/bin:$PATH"
|
||||
|
||||
pushd "$openpype_root" > /dev/null || return > /dev/null
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
|
||||
if [ -f "$POETRY_HOME/bin/poetry" ]; then
|
||||
echo -e "${BIGreen}OK${RST}"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
fi
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..."
|
||||
poetry run python3 "$openpype_root/start.py" settings --dev
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,14 @@ $current_dir = Get-Location
|
|||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
$version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
|
||||
|
|
@ -69,34 +77,20 @@ if (-not $openpype_version) {
|
|||
}
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Building OpenPype [ " -NoNewline -ForegroundColor white
|
||||
Write-Host "OpenPype [ " -NoNewline -ForegroundColor white
|
||||
Write-host $openpype_version -NoNewline -ForegroundColor green
|
||||
Write-Host " ] ..." -ForegroundColor white
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Detecting host Python ... " -NoNewline
|
||||
if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "!!! Python not detected" -ForegroundColor red
|
||||
Exit-WithCode 1
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
$version_command = @"
|
||||
import sys
|
||||
print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))
|
||||
"@
|
||||
|
||||
$p = & python -c $version_command
|
||||
$env:PYTHON_VERSION = $p
|
||||
$m = $p -match '(\d+)\.(\d+)'
|
||||
if(-not $m) {
|
||||
Write-Host "!!! Cannot determine version" -ForegroundColor red
|
||||
Exit-WithCode 1
|
||||
}
|
||||
# We are supporting python 3.6 and up
|
||||
if(($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
|
||||
Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red
|
||||
Exit-WithCode 1
|
||||
}
|
||||
Write-Host "OK [ $p ]" -ForegroundColor green
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Cleaning cache files ... " -NoNewline
|
||||
|
|
|
|||
|
|
@ -57,32 +57,6 @@ BIPurple='\033[1;95m' # Purple
|
|||
BICyan='\033[1;96m' # Cyan
|
||||
BIWhite='\033[1;97m' # White
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Detect required version of python
|
||||
# Globals:
|
||||
# colors
|
||||
# PYTHON
|
||||
# Arguments:
|
||||
# None
|
||||
# Returns:
|
||||
# None
|
||||
###############################################################################
|
||||
detect_python () {
|
||||
echo -e "${BIGreen}>>>${RST} Using python \c"
|
||||
local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))"
|
||||
local python_version="$(python3 <<< ${version_command})"
|
||||
oIFS="$IFS"
|
||||
IFS=.
|
||||
set -- $python_version
|
||||
IFS="$oIFS"
|
||||
if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then
|
||||
echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}"
|
||||
else
|
||||
command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}FAILED${RST} ${BIYellow} Version [${RST}${BICyan}$1.$2${RST}]${BIYellow} is old and unsupported${RST}"; return 1; }
|
||||
fi
|
||||
}
|
||||
|
||||
##############################################################################
|
||||
# Clean pyc files in specified directory
|
||||
# Globals:
|
||||
|
|
@ -118,10 +92,27 @@ main () {
|
|||
echo -e "${BGreen}"
|
||||
art
|
||||
echo -e "${RST}"
|
||||
detect_python || return 1
|
||||
|
||||
# Directories
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
||||
_inside_openpype_tool="1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if [[ -z $POETRY_HOME ]]; then
|
||||
export POETRY_HOME="$openpype_root/.poetry"
|
||||
fi
|
||||
export PATH="$POETRY_HOME/bin:$PATH"
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
|
||||
if [ -f "$POETRY_HOME/bin/poetry" ]; then
|
||||
echo -e "${BIGreen}OK${RST}"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
fi
|
||||
|
||||
pushd "$openpype_root" || return > /dev/null
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Testing OpenPype ..."
|
||||
|
|
|
|||
|
|
@ -13,7 +13,27 @@ PS> .\run_tray.ps1
|
|||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
$env:_INSIDE_OPENPYPE_TOOL = "1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if (-not (Test-Path 'env:POETRY_HOME')) {
|
||||
$env:POETRY_HOME = "$openpype_root\.poetry"
|
||||
}
|
||||
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
|
||||
|
||||
Set-Location -Path $openpype_root
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor Green
|
||||
Write-Host "Reading Poetry ... " -NoNewline
|
||||
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
|
||||
Write-Host "NOT FOUND" -ForegroundColor Yellow
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "We need to install Poetry create virtual env first ..."
|
||||
& "$openpype_root\tools\create_env.ps1"
|
||||
} else {
|
||||
Write-Host "OK" -ForegroundColor Green
|
||||
}
|
||||
|
||||
& poetry run python "$($openpype_root)\start.py" tray --debug
|
||||
Set-Location -Path $current_dir
|
||||
|
|
@ -53,6 +53,24 @@ realpath () {
|
|||
main () {
|
||||
# Directories
|
||||
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
||||
_inside_openpype_tool="1"
|
||||
|
||||
# make sure Poetry is in PATH
|
||||
if [[ -z $POETRY_HOME ]]; then
|
||||
export POETRY_HOME="$openpype_root/.poetry"
|
||||
fi
|
||||
export PATH="$POETRY_HOME/bin:$PATH"
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
|
||||
if [ -f "$POETRY_HOME/bin/poetry" ]; then
|
||||
echo -e "${BIGreen}OK${RST}"
|
||||
else
|
||||
echo -e "${BIYellow}NOT FOUND${RST}"
|
||||
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
|
||||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
fi
|
||||
|
||||
pushd "$openpype_root" > /dev/null || return > /dev/null
|
||||
|
||||
echo -e "${BIGreen}>>>${RST} Running OpenPype Tray with debug option ..."
|
||||
|
|
|
|||
208
website/docs/artist_hosts_tvpaint.md
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
---
|
||||
id: artist_hosts_tvpaint
|
||||
title: TVPaint
|
||||
sidebar_label: TVPaint
|
||||
---
|
||||
|
||||
- [Work Files](artist_tools.md#workfiles)
|
||||
- [Load](artist_tools.md#loader)
|
||||
- [Create](artist_tools.md#creator)
|
||||
- [Subset Manager](artist_tools.md#subset-manager)
|
||||
- [Scene Inventory](artist_tools.md#scene-inventory)
|
||||
- [Publish](artist_tools.md#publisher)
|
||||
- [Library](artist_tools.md#library)
|
||||
|
||||
|
||||
## Setup
|
||||
When you launch TVPaint with OpenPype for the very first time it is necessary to do some additional steps. Right after the TVPaint launching a few system windows will pop up.
|
||||
|
||||

|
||||
|
||||
Choose `Replace the file in the destination`. Then another window shows up.
|
||||
|
||||

|
||||
|
||||
Click on `Continue`.
|
||||
|
||||
After opening TVPaint go to the menu bar: `Windows → Plugins → OpenPype`.
|
||||
|
||||

|
||||
|
||||
Another TVPaint window pop up. Please press `Yes`. This window will be presented in every single TVPaint launching. Unfortunately, there is no other way how to workaround it.
|
||||
|
||||

|
||||
|
||||
Now OpenPype Tools menu is in your TVPaint work area.
|
||||
|
||||

|
||||
|
||||
You can start your work.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
In TVPaint you can find the Tools in OpenPype menu extension. The OpenPype Tools menu should be available in your work area. However, sometimes it happens that the Tools menu is hidden. You can display the extension panel by going to `Windows -> Plugins -> OpenPype`.
|
||||
|
||||
|
||||
## Create
|
||||
In TVPaint you can create and publish **[Reviews](#review)**, **[Render Passes](#render-pass)**, and **[Render Layers](#render-layer)**.
|
||||
|
||||
You have the possibility to organize your layers by using `Color group`.
|
||||
|
||||
On the bottom left corner of your timeline, you will note a `Color group` button.
|
||||
|
||||

|
||||
|
||||
It allows you to choose a group by checking one of the colors of the color list.
|
||||
|
||||

|
||||
|
||||
The timeline's animation layer can be marked by the color you pick from your Color group. Layers in the timeline with the same color are gathered into a group represents one render layer.
|
||||
|
||||

|
||||
|
||||
:::important
|
||||
OpenPype specifically never tries to guess what you want to publish from the scene. Therefore, you have to tell OpenPype what you want to publish. There are three ways how to publish render from the scene.
|
||||
:::
|
||||
|
||||
When you want to publish `review` or `render layer` or `render pass`, open the `Creator` through the Tools menu `Create` button.
|
||||
|
||||
### Review
|
||||
|
||||
<div class="row markdown">
|
||||
<div class="col col--6 markdown">
|
||||
|
||||
`Review` renders the whole file as is and sends the resulting QuickTime to Ftrack.
|
||||
|
||||
To create reviewable quicktime of your animation:
|
||||
|
||||
- select `Review` in the `Creator`
|
||||
- press `Create`
|
||||
- When you run [publish](#publish), file will be rendered and converted to quicktime.`
|
||||
|
||||
</div>
|
||||
<div class="col col--6 markdown">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Render Layer
|
||||
|
||||
<div class="row markdown">
|
||||
<div class="col col--6 markdown">
|
||||
|
||||
|
||||
Render Layer bakes all the animation layers of one particular color group together.
|
||||
|
||||
- Choose any amount of animation layers that need to be rendered together and assign them a color group.
|
||||
- Select any layer of a particular color
|
||||
- Go to `Creator` and choose `RenderLayer`.
|
||||
- In the `Subset`, type in the name that the final published RenderLayer should have according to the naming convention in your studio. *(L10, BG, Hero, etc.)*
|
||||
- Press `Create`
|
||||
- When you run [publish](#publish), the whole color group will be rendered together and published as a single `RenderLayer`
|
||||
|
||||
</div>
|
||||
<div class="col col--6 markdown">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Render Pass
|
||||
|
||||
Render Passes are smaller individual elements of a Render Layer. A `character` render layer might
|
||||
consist of multiple render passes such as `Line`, `Color` and `Shadow`.
|
||||
|
||||
|
||||
<div class="row markdown">
|
||||
<div class="col col--6 markdown">
|
||||
Render Passes are specific because they have to belong to a particular layer. If you try to create a render pass and did not create any render layers before, an error message will pop up.
|
||||
|
||||
When you want to create `RenderPass`
|
||||
- choose one or several animation layers within one color group that you want to publish
|
||||
- In the Creator, pick `RenderPass`
|
||||
- Fill the `Subset` with the name of your pass, e.g. `Color`.
|
||||
- Press `Create`
|
||||
|
||||
</div>
|
||||
<div class="col col--6 markdown">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br></br>
|
||||
|
||||
In this example, OpenPype will render selected animation layers within the given color group. E.i. the layers *L020_colour_fx*, *L020_colour_mouth*, and *L020_colour_eye* will be rendered as one pass belonging to the yellow RenderLayer.
|
||||
|
||||

|
||||
|
||||
|
||||
:::note
|
||||
You can check your RendrePasses and RenderLayers in [Subset Manager](#subset-manager) or you can start publishing. The publisher will show you a collection of all instances on the left side.
|
||||
:::
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Publish
|
||||
|
||||
<div class="row markdown">
|
||||
<div class="col col--6 markdown">
|
||||
|
||||
Now that you have created the required instances, you can publish them via `Publish` tool.
|
||||
- Click on `Publish` in OpenPype Tools menu.
|
||||
- wait until all instances are collected.
|
||||
- You can check on the left side whether all your instances have been created and are ready for publishing.
|
||||
- Fill the comment on the bottom of the window.
|
||||
- Press the `Play` button to publish
|
||||
|
||||
</div>
|
||||
<div class="col col--6 markdown">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Once the `Publisher` turns gets green your renders have been published.
|
||||
|
||||
---
|
||||
|
||||
## Subset Manager
|
||||
All created instances (render layers, passes, and reviews) will be shown as a simple list. If you don't want to publish some, right click on the item in the list and select `Remove instance`.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Load
|
||||
When you want to load existing published work you can reach the `Loader` through the OpenPype Tools `Load` button.
|
||||
|
||||
The supported families for TVPaint are:
|
||||
|
||||
- `render`
|
||||
- `image`
|
||||
- `background`
|
||||
- `plate`
|
||||
|
||||
To load a family item, right-click on the subset you want and import their representations, switch among the versions, delete older versions, copy files, etc.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Scene Inventory
|
||||
Scene Inventory shows you everything that you have loaded into your scene using OpenPype. You can reach it through the extension's `Scene Inventory` button.
|
||||
|
||||

|
||||
|
||||
You can switch to a previous version of the file or update it to the latest or delete items.
|
||||
BIN
website/docs/assets/tvp_asset_loader_actions.png
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
website/docs/assets/tvp_asset_loader_version.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
website/docs/assets/tvp_color_groups.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
website/docs/assets/tvp_color_groups2.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
website/docs/assets/tvp_create_layer.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
website/docs/assets/tvp_create_pass.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
website/docs/assets/tvp_create_review.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
website/docs/assets/tvp_hidden_window.gif
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
website/docs/assets/tvp_library.gif
Normal file
|
After Width: | Height: | Size: 232 KiB |
BIN
website/docs/assets/tvp_loader.gif
Normal file
|
After Width: | Height: | Size: 651 KiB |
BIN
website/docs/assets/tvp_openpype_menu.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
website/docs/assets/tvp_permission.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
website/docs/assets/tvp_permission2.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
website/docs/assets/tvp_pyblish.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
website/docs/assets/tvp_pyblish_render.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
website/docs/assets/tvp_render_pass.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
website/docs/assets/tvp_scene_inventory.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
website/docs/assets/tvp_subset_manager.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
website/docs/assets/tvp_timeline_color.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
website/docs/assets/tvp_timeline_color2.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
website/docs/assets/tvp_write_file.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
|
@ -23,6 +23,7 @@ module.exports = {
|
|||
"artist_hosts_harmony",
|
||||
"artist_hosts_aftereffects",
|
||||
"artist_hosts_photoshop",
|
||||
"artist_hosts_tvpaint",
|
||||
"artist_hosts_unreal",
|
||||
{
|
||||
type: "category",
|
||||
|
|
|
|||