Merge branch 'develop' into release/3.0.0

This commit is contained in:
Milan Kolar 2021-06-03 17:55:05 +02:00
commit 1280176c0d
26 changed files with 342 additions and 68 deletions

1
.gitignore vendored
View file

@ -36,6 +36,7 @@ Temporary Items
# CX_Freeze
###########
/build
/dist/
/vendor/bin/*
/.venv

View file

@ -97,7 +97,8 @@ from .local_settings import (
OpenPypeSettingsRegistry,
get_local_site_id,
change_openpype_mongo_url,
get_openpype_username
get_openpype_username,
is_admin_password_required
)
from .applications import (
@ -209,6 +210,7 @@ __all__ = [
"get_local_site_id",
"change_openpype_mongo_url",
"get_openpype_username",
"is_admin_password_required",
"ApplicationLaunchFailed",
"ApplictionExecutableNotFound",

View file

@ -29,7 +29,10 @@ except ImportError:
import six
import appdirs
from openpype.settings import get_local_settings
from openpype.settings import (
get_local_settings,
get_system_settings
)
from .import validate_mongo_connection
@ -562,3 +565,16 @@ def get_openpype_username():
if not username:
username = getpass.getuser()
return username
def is_admin_password_required():
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

View file

@ -36,6 +36,7 @@ from .clockify import ClockifyModule
from .log_viewer import LogViewModule
from .muster import MusterModule
from .deadline import DeadlineModule
from .project_manager_action import ProjectManagerAction
from .standalonepublish_action import StandAlonePublishAction
from .sync_server import SyncServerModule
@ -73,6 +74,7 @@ __all__ = (
"LogViewModule",
"MusterModule",
"DeadlineModule",
"ProjectManagerAction",
"StandAlonePublishAction",
"SyncServerModule"

View file

@ -86,7 +86,7 @@ class AvalonModule(PypeModule, ITrayModule, IWebServerRoutes):
from Qt import QtWidgets
# Actions
action_library_loader = QtWidgets.QAction(
"Library loader", tray_menu
"Loader", tray_menu
)
action_library_loader.triggered.connect(self.show_library_loader)

View file

@ -172,6 +172,10 @@ class ITrayModule:
if self._tray_manager:
self._tray_manager.show_tray_message(title, message, icon, msecs)
def add_doubleclick_callback(self, callback):
if hasattr(self.manager, "add_doubleclick_callback"):
self.manager.add_doubleclick_callback(self, callback)
class ITrayAction(ITrayModule):
"""Implementation of Tray action.
@ -184,6 +188,9 @@ class ITrayAction(ITrayModule):
necessary.
"""
admin_action = False
_admin_submenu = None
@property
@abstractmethod
def label(self):
@ -197,9 +204,19 @@ class ITrayAction(ITrayModule):
def tray_menu(self, tray_menu):
from Qt import QtWidgets
action = QtWidgets.QAction(self.label, tray_menu)
if self.admin_action:
menu = self.admin_submenu(tray_menu)
action = QtWidgets.QAction(self.label, menu)
menu.addAction(action)
if not menu.menuAction().isVisible():
menu.menuAction().setVisible(True)
else:
action = QtWidgets.QAction(self.label, tray_menu)
tray_menu.addAction(action)
action.triggered.connect(self.on_action_trigger)
tray_menu.addAction(action)
def tray_start(self):
return
@ -207,6 +224,16 @@ class ITrayAction(ITrayModule):
def tray_exit(self):
return
@staticmethod
def admin_submenu(tray_menu):
if ITrayAction._admin_submenu is None:
from Qt import QtWidgets
admin_submenu = QtWidgets.QMenu("Admin", tray_menu)
admin_submenu.menuAction().setVisible(False)
ITrayAction._admin_submenu = admin_submenu
return ITrayAction._admin_submenu
class ITrayService(ITrayModule):
# Module's property
@ -233,6 +260,7 @@ class ITrayService(ITrayModule):
def services_submenu(tray_menu):
if ITrayService._services_submenu is None:
from Qt import QtWidgets
services_submenu = QtWidgets.QMenu("Services", tray_menu)
services_submenu.menuAction().setVisible(False)
ITrayService._services_submenu = services_submenu
@ -677,7 +705,7 @@ class TrayModulesManager(ModulesManager):
)
def __init__(self):
self.log = PypeLogger().get_logger(self.__class__.__name__)
self.log = PypeLogger.get_logger(self.__class__.__name__)
self.modules = []
self.modules_by_id = {}
@ -685,6 +713,28 @@ class TrayModulesManager(ModulesManager):
self._report = {}
self.tray_manager = None
self.doubleclick_callbacks = {}
self.doubleclick_callback = None
def add_doubleclick_callback(self, module, callback):
"""Register doubleclick callbacks on tray icon.
Currently there is no way how to determine which is launched. Name of
callback can be defined with `doubleclick_callback` attribute.
Missing feature how to define default callback.
"""
callback_name = "_".join([module.name, callback.__name__])
if callback_name not in self.doubleclick_callbacks:
self.doubleclick_callbacks[callback_name] = callback
if self.doubleclick_callback is None:
self.doubleclick_callback = callback_name
return
self.log.warning((
"Callback with name \"{}\" is already registered."
).format(callback_name))
def initialize(self, tray_manager, tray_menu):
self.tray_manager = tray_manager
self.initialize_modules()

View file

@ -20,7 +20,6 @@ class CollectFtrackApi(pyblish.api.ContextPlugin):
# NOTE Import python module here to know if import was successful
import ftrack_api
session = ftrack_api.Session(auto_connect_event_hub=False)
session = ftrack_api.Session(auto_connect_event_hub=False)
self.log.debug("Ftrack user: \"{0}\"".format(session.api_user))

View file

@ -15,6 +15,8 @@ class LauncherAction(PypeModule, ITrayAction):
def tray_init(self):
self.create_window()
self.add_doubleclick_callback(self.show_launcher)
def tray_start(self):
return

View file

@ -0,0 +1,59 @@
from . import PypeModule, ITrayAction
class ProjectManagerAction(PypeModule, ITrayAction):
label = "Project Manager (beta)"
name = "project_manager"
admin_action = True
def initialize(self, modules_settings):
enabled = False
module_settings = modules_settings.get(self.name)
if module_settings:
enabled = module_settings.get("enabled", enabled)
self.enabled = enabled
# Tray attributes
self.project_manager_window = None
def connect_with_modules(self, *_a, **_kw):
return
def tray_init(self):
"""Initialization in tray implementation of ITrayAction."""
self.create_project_manager_window()
def on_action_trigger(self):
"""Implementation for action trigger of ITrayAction."""
self.show_project_manager_window()
def create_project_manager_window(self):
"""Initializa Settings Qt window."""
if self.project_manager_window:
return
from openpype.tools.project_manager import ProjectManagerWindow
self.project_manager_window = ProjectManagerWindow()
def show_project_manager_window(self):
"""Show project manager tool window.
Raises:
AssertionError: Window must be already created. Call
`create_project_manager_window` before calling this method.
"""
if not self.project_manager_window:
raise AssertionError("Window is not initialized.")
# Store if was visible
was_minimized = self.project_manager_window.isMinimized()
# Show settings gui
self.project_manager_window.show()
if was_minimized:
self.project_manager_window.showNormal()
# Pull window to the front.
self.project_manager_window.raise_()
self.project_manager_window.activateWindow()

View file

@ -37,7 +37,8 @@ class ISettingsChangeListener:
class SettingsAction(PypeModule, ITrayAction):
"""Action to show Setttings tool."""
name = "settings"
label = "Settings"
label = "Studio Settings"
admin_action = True
def initialize(self, _modules_settings):
# This action is always enabled
@ -78,7 +79,7 @@ class SettingsAction(PypeModule, ITrayAction):
Raises:
AssertionError: Window must be already created. Call
`create_settings_window` before callint this method.
`create_settings_window` before calling this method.
"""
if not self.settings_window:
raise AssertionError("Window is not initialized.")
@ -105,7 +106,7 @@ class SettingsAction(PypeModule, ITrayAction):
class LocalSettingsAction(PypeModule, ITrayAction):
"""Action to show Setttings tool."""
name = "local_settings"
label = "Local Settings"
label = "Settings"
def initialize(self, _modules_settings):
# This action is always enabled

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

@ -164,5 +164,8 @@
},
"standalonepublish_tool": {
"enabled": true
},
"project_manager": {
"enabled": true
}
}

View file

@ -192,6 +192,20 @@
"label": "Enabled"
}
]
},
{
"type": "dict",
"key": "project_manager",
"label": "Project Manager (beta)",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
}
]
}

View file

@ -504,6 +504,21 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
#IconBtn {}
/* Password dialog*/
#PasswordBtn {
border: none;
padding:0.1em;
background: transparent;
}
#PasswordBtn:hover {
background: {color:bg-buttons};
}
#RememberCheckbox {
spacing: 0.5em;
}
/* Project Manager stylesheets */
#HierarchyView::item {
padding-top: 3px;

View file

@ -11,6 +11,8 @@ from . import (
)
from openpype.style import load_stylesheet
from .style import ResourceCache
from openpype.lib import is_admin_password_required
from openpype.widgets import PasswordDialog
from openpype import resources
from avalon.api import AvalonMongoDB
@ -20,6 +22,10 @@ class ProjectManagerWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ProjectManagerWindow, self).__init__(parent)
self._initial_reset = False
self._password_dialog = None
self._user_passed = False
self.setWindowTitle("OpenPype Project Manager")
self.setWindowIcon(QtGui.QIcon(resources.pype_icon_filepath()))
@ -33,6 +39,9 @@ class ProjectManagerWindow(QtWidgets.QWidget):
project_model = ProjectModel(dbcon)
project_combobox = QtWidgets.QComboBox(project_widget)
project_combobox.setSizeAdjustPolicy(
QtWidgets.QComboBox.AdjustToContents
)
project_combobox.setModel(project_model)
project_combobox.setRootModelIndex(QtCore.QModelIndex())
style_delegate = QtWidgets.QStyledItemDelegate()
@ -135,13 +144,15 @@ class ProjectManagerWindow(QtWidgets.QWidget):
self.resize(1200, 600)
self.setStyleSheet(load_stylesheet())
self.refresh_projects()
def _set_project(self, project_name=None):
self.hierarchy_view.set_project(project_name)
def showEvent(self, event):
super(ProjectManagerWindow, self).showEvent(event)
if not self._initial_reset:
self.reset()
font_size = self._refresh_projects_btn.fontMetrics().height()
icon_size = QtCore.QSize(font_size, font_size)
self._refresh_projects_btn.setIconSize(icon_size)
@ -193,3 +204,45 @@ class ProjectManagerWindow(QtWidgets.QWidget):
project_name = dialog.project_name
self.show_message("Created project \"{}\"".format(project_name))
self.refresh_projects(project_name)
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_admin_password_required()
if not self._user_passed:
self.setEnabled(False)
# 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
self.setEnabled(True)
# Mark as was reset
if not self._initial_reset:
self._initial_reset = True
self.refresh_projects()

View file

@ -1,11 +1,9 @@
import sys
from Qt import QtWidgets, QtGui
from .lib import (
is_password_required,
BTN_FIXED_SIZE,
CHILD_OFFSET
)
from .widgets import PasswordDialog
from .local_settings import LocalSettingsWindow
from .settings import (
style,
@ -35,13 +33,11 @@ def main(user_role=None):
__all__ = (
"is_password_required",
"BTN_FIXED_SIZE",
"CHILD_OFFSET",
"style",
"PasswordDialog",
"MainWidget",
"ProjectListWidget",
"LocalSettingsWindow",

View file

@ -1,20 +1,2 @@
CHILD_OFFSET = 15
BTN_FIXED_SIZE = 20
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

View file

@ -1,10 +1,8 @@
import getpass
from Qt import QtWidgets, QtCore
from openpype.tools.settings import (
is_password_required,
PasswordDialog
)
from openpype.lib import is_admin_password_required
from openpype.widgets import PasswordDialog
class LocalGeneralWidgets(QtWidgets.QWidget):
@ -57,7 +55,7 @@ class LocalGeneralWidgets(QtWidgets.QWidget):
if not self.is_admin_input.isChecked():
return
if not is_password_required():
if not is_admin_password_required():
return
dialog = PasswordDialog(self, False)

View file

@ -7,10 +7,8 @@ from .categories import (
from .widgets import ShadowWidget, RestartDialog
from . import style
from openpype.tools.settings import (
is_password_required,
PasswordDialog
)
from openpype.lib import is_admin_password_required
from openpype.widgets import PasswordDialog
class MainWidget(QtWidgets.QWidget):
@ -117,7 +115,7 @@ class MainWidget(QtWidgets.QWidget):
return
if not self._user_passed:
self._user_passed = not is_password_required()
self._user_passed = not is_admin_password_required()
self._on_state_change()

View file

@ -15,7 +15,11 @@ from openpype.api import (
get_system_settings
)
from openpype.lib import get_pype_execute_args
from openpype.modules import TrayModulesManager, ITrayService
from openpype.modules import (
TrayModulesManager,
ITrayAction,
ITrayService
)
from openpype import style
from .pype_info_widget import PypeInfoWidget
@ -44,6 +48,18 @@ class TrayManager:
self._main_thread_callbacks = collections.deque()
self._execution_in_progress = None
@property
def doubleclick_callback(self):
"""Doubleclick callback for Tray icon."""
callback_name = self.modules_manager.doubleclick_callback
return self.modules_manager.doubleclick_callbacks.get(callback_name)
def execute_doubleclick(self):
"""Execute double click callback in main thread."""
callback = self.doubleclick_callback
if callback:
self.execute_in_main_thread(callback)
def execute_in_main_thread(self, callback):
self._main_thread_callbacks.append(callback)
@ -67,6 +83,9 @@ class TrayManager:
self.modules_manager.initialize(self, self.tray_widget.menu)
admin_submenu = ITrayAction.admin_submenu(self.tray_widget.menu)
self.tray_widget.menu.addMenu(admin_submenu)
# Add services if they are
services_submenu = ITrayService.services_submenu(self.tray_widget.menu)
self.tray_widget.menu.addMenu(services_submenu)
@ -178,6 +197,8 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
:type parent: QtWidgets.QMainWindow
"""
doubleclick_time_ms = 100
def __init__(self, parent):
icon = QtGui.QIcon(resources.pype_icon_filepath())
@ -196,20 +217,50 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
self.tray_man = TrayManager(self, self.parent)
self.tray_man.initialize_modules()
# Catch activate event for left click if not on MacOS
# - MacOS has this ability by design so menu would be doubled
if platform.system().lower() != "darwin":
self.activated.connect(self.on_systray_activated)
# Add menu to Context of SystemTrayIcon
self.setContextMenu(self.menu)
atexit.register(self.exit)
# Catch activate event for left click if not on MacOS
# - MacOS has this ability by design and is harder to modify this
# behavior
if platform.system().lower() == "darwin":
return
self.activated.connect(self.on_systray_activated)
click_timer = QtCore.QTimer()
click_timer.setInterval(self.doubleclick_time_ms)
click_timer.timeout.connect(self._click_timer_timeout)
self._click_timer = click_timer
self._doubleclick = False
def _click_timer_timeout(self):
self._click_timer.stop()
doubleclick = self._doubleclick
# Reset bool value
self._doubleclick = False
if doubleclick:
self.tray_man.execute_doubleclick()
else:
self._show_context_menu()
def _show_context_menu(self):
pos = QtGui.QCursor().pos()
self.contextMenu().popup(pos)
def on_systray_activated(self, reason):
# show contextMenu if left click
if reason == QtWidgets.QSystemTrayIcon.Trigger:
position = QtGui.QCursor().pos()
self.contextMenu().popup(position)
if self.tray_man.doubleclick_callback:
self._click_timer.start()
else:
self._show_context_menu()
elif reason == QtWidgets.QSystemTrayIcon.DoubleClick:
self._doubleclick = True
def exit(self):
""" Exit whole application.

View file

@ -0,0 +1,6 @@
from .password_dialog import PasswordDialog
__all__ = (
"PasswordDialog",
)

View file

@ -1,6 +1,7 @@
from Qt import QtWidgets, QtCore, QtGui
from .resources import get_resource
from openpype import style
from openpype.resources import get_resource
from openpype.api import get_system_settings
from openpype.settings.lib import (
@ -43,7 +44,7 @@ class PasswordDialog(QtWidgets.QDialog):
def __init__(self, parent=None, allow_remember=True):
super(PasswordDialog, self).__init__(parent)
self.setWindowTitle("Settings Password")
self.setWindowTitle("Admin Password")
self.resize(300, 120)
system_settings = get_system_settings()
@ -62,13 +63,11 @@ class PasswordDialog(QtWidgets.QDialog):
password_input = QtWidgets.QLineEdit(password_widget)
password_input.setEchoMode(QtWidgets.QLineEdit.Password)
show_password_icon_path = get_resource("images", "eye.png")
show_password_icon_path = get_resource("icons", "eye.png")
show_password_icon = QtGui.QIcon(show_password_icon_path)
show_password_btn = PressHoverButton(password_widget)
show_password_btn.setObjectName("PasswordBtn")
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)
@ -83,10 +82,8 @@ class PasswordDialog(QtWidgets.QDialog):
buttons_widget = QtWidgets.QWidget(self)
remember_checkbox = QtWidgets.QCheckBox("Remember", buttons_widget)
remember_checkbox.setObjectName("RememberCheckbox")
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)
@ -114,6 +111,8 @@ class PasswordDialog(QtWidgets.QDialog):
self.remember_checkbox = remember_checkbox
self.message_label = message_label
self.setStyleSheet(style.load_stylesheet())
def remember_password(self):
if not self._allow_remember:
return False

View file

@ -9,6 +9,23 @@ documentation = "https://openpype.io/docs/artist_getting_started"
repository = "https://github.com/pypeclub/openpype"
readme = "README.md"
keywords = ["Pipeline", "Avalon", "VFX", "animation", "automation", "tracking", "asset management"]
packages = [
{include = "igniter"},
{include = "repos"},
{include = "tools"},
{include = "tests"},
{include = "docs"},
{include = "openpype"},
{include = "start.py"},
{include = "LICENSE"},
{include = "README.md"},
{include = "setup.py"},
{include = "pyproject.toml"},
{include = "poetry.lock"}
]
[tool.poetry.scripts]
openpype = 'start:boot'
[tool.poetry.dependencies]
python = "3.7.*"

View file

@ -50,7 +50,9 @@ install_requires = [
]
includes = []
excludes = []
excludes = [
"openpype"
]
bin_includes = []
include_files = [
"igniter",

View file

@ -120,7 +120,7 @@ _print("Copying dependencies ...")
total_files = count_folders(site_pkg)
progress_bar = enlighten.Counter(
total=total_files, desc="Processing Dependencies",
units="%", color="green")
units="%", color=(53, 178, 202))
def _progress(_base, _names):
@ -140,7 +140,8 @@ to_delete = []
deps_items = list(deps_dir.iterdir())
item_count = len(list(libs_dir.iterdir()))
find_progress_bar = enlighten.Counter(
total=item_count, desc="Finding duplicates", units="%", color="yellow")
total=item_count, desc="Finding duplicates", units="%",
color=(56, 211, 159))
for d in libs_dir.iterdir():
if (deps_dir / d.name) in deps_items:
@ -152,16 +153,23 @@ find_progress_bar.close()
# add openpype and igniter in libs too
to_delete.append(libs_dir / "openpype")
to_delete.append(libs_dir / "igniter")
to_delete.append(libs_dir / "openpype.pth")
to_delete.append(deps_dir / "openpype.pth")
# delete duplicates
# _print(f"Deleting {len(to_delete)} duplicates ...")
delete_progress_bar = enlighten.Counter(
total=len(to_delete), desc="Deleting duplicates", units="%", color="red")
total=len(to_delete), desc="Deleting duplicates", units="%",
color=(251, 192, 32))
for d in to_delete:
if d.is_dir():
shutil.rmtree(d)
else:
d.unlink()
try:
d.unlink()
except FileNotFoundError:
# skip non-existent silently
pass
delete_progress_bar.update()
delete_progress_bar.close()

View file

@ -8754,9 +8754,9 @@ write-file-atomic@^3.0.0:
typedarray-to-buffer "^3.1.5"
ws@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
version "6.2.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e"
integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==
dependencies:
async-limiter "~1.0.0"