diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py
index d82b7cd847..bed57d7022 100644
--- a/openpype/lib/applications.py
+++ b/openpype/lib/applications.py
@@ -191,26 +191,32 @@ class Application:
self.full_label = full_label
self._environment = data.get("environment") or {}
+ arguments = data.get("arguments")
+ if isinstance(arguments, dict):
+ arguments = arguments.get(platform.system().lower())
+
+ if not arguments:
+ arguments = []
+ self.arguments = arguments
+
+ if "executables" not in data:
+ self.executables = [
+ UndefinedApplicationExecutable()
+ ]
+ return
+
_executables = data["executables"]
+ if isinstance(_executables, dict):
+ _executables = _executables.get(platform.system().lower())
+
if not _executables:
_executables = []
- elif isinstance(_executables, dict):
- _executables = _executables.get(platform.system().lower()) or []
-
- _arguments = data["arguments"]
- if not _arguments:
- _arguments = []
-
- elif isinstance(_arguments, dict):
- _arguments = _arguments.get(platform.system().lower()) or []
-
executables = []
for executable in _executables:
executables.append(ApplicationExecutable(executable))
self.executables = executables
- self.arguments = _arguments
def __repr__(self):
return "<{}> - {}".format(self.__class__.__name__, self.full_name)
@@ -484,6 +490,27 @@ class ApplicationExecutable:
return bool(self._realpath())
+class UndefinedApplicationExecutable(ApplicationExecutable):
+ """Some applications do not require executable path from settings.
+
+ In that case this class is used to "fake" existing executable.
+ """
+ def __init__(self):
+ pass
+
+ def __str__(self):
+ return self.__class__.__name__
+
+ def __repr__(self):
+ return "<{}>".format(self.__class__.__name__)
+
+ def as_args(self):
+ return []
+
+ def exists(self):
+ return True
+
+
@six.add_metaclass(ABCMeta)
class LaunchHook:
"""Abstract base class of launch hook."""
diff --git a/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py b/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py
index d20e2ff5a8..f215bedcc2 100644
--- a/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py
+++ b/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py
@@ -66,15 +66,7 @@ class VersionToTaskStatus(BaseEvent):
))
return
- _status_mapping = event_settings["mapping"]
- if not _status_mapping:
- self.log.debug(
- "Project \"{}\" does not have set mapping for {}".format(
- project_name, self.__class__.__name__
- )
- )
- return
-
+ _status_mapping = event_settings["mapping"] or {}
status_mapping = {
key.lower(): value
for key, value in _status_mapping.items()
diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py
index 5d1da005dc..2458308af5 100644
--- a/openpype/modules/ftrack/lib/avalon_sync.py
+++ b/openpype/modules/ftrack/lib/avalon_sync.py
@@ -402,16 +402,18 @@ class SyncEntitiesFactory:
items = []
items.append({
"type": "label",
- "value": "# Can't access Custom attribute <{}>".format(
- CUST_ATTR_ID_KEY
- )
+ "value": (
+ "# Can't access Custom attribute: \"{}\""
+ ).format(CUST_ATTR_ID_KEY)
})
items.append({
"type": "label",
"value": (
- "
- Check if user \"{}\" has permissions"
- " to access the Custom attribute
"
- ).format(self._api_key)
+ "- Check if your User and API key has permissions"
+ " to access the Custom attribute."
+ "
Username:\"{}\""
+ "
API key:\"{}\"
"
+ ).format(self._api_user, self._api_key)
})
items.append({
"type": "label",
diff --git a/openpype/modules/settings_action.py b/openpype/modules/settings_action.py
index 1035dc0dcd..9db4a252bc 100644
--- a/openpype/modules/settings_action.py
+++ b/openpype/modules/settings_action.py
@@ -114,6 +114,7 @@ class LocalSettingsAction(PypeModule, ITrayAction):
# Tray attributes
self.settings_window = None
+ self._first_trigger = True
def connect_with_modules(self, *_a, **_kw):
return
@@ -153,6 +154,9 @@ class LocalSettingsAction(PypeModule, ITrayAction):
self.settings_window.raise_()
self.settings_window.activateWindow()
- # Reset content if was not visible
- if not was_visible:
+ # Do not reset if it's first trigger of action
+ if self._first_trigger:
+ self._first_trigger = False
+ elif not was_visible:
+ # Reset content if was not visible
self.settings_window.reset()
diff --git a/openpype/settings/defaults/project_settings/harmony.json b/openpype/settings/defaults/project_settings/harmony.json
index 0c7a35c058..3cc175ae72 100644
--- a/openpype/settings/defaults/project_settings/harmony.json
+++ b/openpype/settings/defaults/project_settings/harmony.json
@@ -5,6 +5,11 @@
".*"
]
},
+ "ValidateContainers": {
+ "enabled": true,
+ "optional": true,
+ "active": true
+ },
"ValidateSceneSettings": {
"enabled": true,
"optional": true,
diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json
new file mode 100644
index 0000000000..811a446e59
--- /dev/null
+++ b/openpype/settings/defaults/project_settings/houdini.json
@@ -0,0 +1,9 @@
+{
+ "publish": {
+ "ValidateContainers": {
+ "enabled": true,
+ "optional": true,
+ "active": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json
index ba685ae502..284a1a0040 100644
--- a/openpype/settings/defaults/project_settings/maya.json
+++ b/openpype/settings/defaults/project_settings/maya.json
@@ -127,6 +127,11 @@
"CollectMayaRender": {
"sync_workfile_version": false
},
+ "ValidateContainers": {
+ "enabled": true,
+ "optional": true,
+ "active": true
+ },
"ValidateShaderName": {
"enabled": false,
"regex": "(?P.*)_(.*)_SHD"
diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json
index 6ff732634e..71bf46d5b3 100644
--- a/openpype/settings/defaults/project_settings/nuke.json
+++ b/openpype/settings/defaults/project_settings/nuke.json
@@ -21,6 +21,11 @@
"PreCollectNukeInstances": {
"sync_workfile_version": true
},
+ "ValidateContainers": {
+ "enabled": true,
+ "optional": true,
+ "active": true
+ },
"ValidateKnobs": {
"enabled": false,
"knobs": {
diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json
index b306a757a6..4c36e4bd49 100644
--- a/openpype/settings/defaults/project_settings/photoshop.json
+++ b/openpype/settings/defaults/project_settings/photoshop.json
@@ -7,6 +7,11 @@
}
},
"publish": {
+ "ValidateContainers": {
+ "enabled": true,
+ "optional": true,
+ "active": true
+ },
"ExtractImage": {
"formats": [
"png",
diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json
index 020924db67..72cd010cf2 100644
--- a/openpype/settings/defaults/system_settings/applications.json
+++ b/openpype/settings/defaults/system_settings/applications.json
@@ -1101,16 +1101,6 @@
"variants": {
"4-26": {
"use_python_2": false,
- "executables": {
- "windows": [],
- "darwin": [],
- "linux": []
- },
- "arguments": {
- "windows": [],
- "darwin": [],
- "linux": []
- },
"environment": {}
}
}
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json
index bee9712878..4a8a9d496e 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_main.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json
@@ -82,6 +82,10 @@
"type": "schema",
"name": "schema_project_hiero"
},
+ {
+ "type": "schema",
+ "name": "schema_project_houdini"
+ },
{
"type": "schema",
"name": "schema_project_blender"
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json
index 8b5d638cd8..ce6246a8de 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json
@@ -25,6 +25,16 @@
}
]
},
+ {
+ "type": "schema_template",
+ "name": "template_publish_plugin",
+ "template_data": [
+ {
+ "key": "ValidateContainers",
+ "label": "ValidateContainers"
+ }
+ ]
+ },
{
"type": "dict",
"collapsible": true,
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json
new file mode 100644
index 0000000000..c6de257a61
--- /dev/null
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json
@@ -0,0 +1,27 @@
+{
+ "type": "dict",
+ "collapsible": true,
+ "key": "houdini",
+ "label": "Houdini",
+ "is_file": true,
+ "children": [
+ {
+ "type": "dict",
+ "collapsible": true,
+ "key": "publish",
+ "label": "Publish plugins",
+ "children": [
+ {
+ "type": "schema_template",
+ "name": "template_publish_plugin",
+ "template_data": [
+ {
+ "key": "ValidateContainers",
+ "label": "ValidateContainers"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json
index 4eb6c26dbb..3b65f08ac4 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json
@@ -33,6 +33,16 @@
"key": "publish",
"label": "Publish plugins",
"children": [
+ {
+ "type": "schema_template",
+ "name": "template_publish_plugin",
+ "template_data": [
+ {
+ "key": "ValidateContainers",
+ "label": "ValidateContainers"
+ }
+ ]
+ },
{
"type": "dict",
"collapsible": true,
@@ -50,7 +60,7 @@
"object_type": "text"
}
]
- }
+ }
]
},
{
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json
index 0abcdd2965..5ca7059ee5 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json
@@ -28,7 +28,16 @@
"type": "label",
"label": "Validators"
},
-
+ {
+ "type": "schema_template",
+ "name": "template_publish_plugin",
+ "template_data": [
+ {
+ "key": "ValidateContainers",
+ "label": "ValidateContainers"
+ }
+ ]
+ },
{
"type": "dict",
"collapsible": true,
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json
index 6873ed5190..782179cfd1 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json
@@ -29,6 +29,16 @@
"type": "label",
"label": "Validators"
},
+ {
+ "type": "schema_template",
+ "name": "template_publish_plugin",
+ "template_data": [
+ {
+ "key": "ValidateContainers",
+ "label": "ValidateContainers"
+ }
+ ]
+ },
{
"type": "dict",
"collapsible": true,
diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json
index df5ec0e6fa..133d6c9eaf 100644
--- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json
+++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json
@@ -30,7 +30,12 @@
"children": [
{
"type": "schema_template",
- "name": "template_host_variant_items"
+ "name": "template_host_variant_items",
+ "skip_paths": [
+ "executables",
+ "separator",
+ "arguments"
+ ]
}
]
}
diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant_items.json b/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant_items.json
index ab4d2374a3..409efb006e 100644
--- a/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant_items.json
+++ b/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant_items.json
@@ -14,6 +14,7 @@
"placeholder": "Executable path"
},
{
+ "key": "separator",
"type":"separator"
},
{
diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py
index f61166fa69..4a3e66de33 100644
--- a/openpype/settings/lib.py
+++ b/openpype/settings/lib.py
@@ -532,7 +532,11 @@ def apply_local_settings_on_system_settings(system_settings, local_settings):
variants = system_settings["applications"][app_group_name]["variants"]
for app_name, app_value in value.items():
- if not app_value or app_name not in variants:
+ if (
+ not app_value
+ or app_name not in variants
+ or "executables" not in variants[app_name]
+ ):
continue
executable = app_value.get("executable")
diff --git a/openpype/tools/launcher/lib.py b/openpype/tools/launcher/lib.py
index b4e6a0c3e9..65d40cd0df 100644
--- a/openpype/tools/launcher/lib.py
+++ b/openpype/tools/launcher/lib.py
@@ -15,7 +15,7 @@ provides a bridge between the file-based project inventory and configuration.
"""
import os
-from Qt import QtGui
+from Qt import QtGui, QtCore
from avalon.vendor import qtawesome
from openpype.api import resources
@@ -23,6 +23,51 @@ ICON_CACHE = {}
NOT_FOUND = type("NotFound", (object, ), {})
+class ProjectHandler(QtCore.QObject):
+ """Handler of project model and current project in Launcher tool.
+
+ Helps to organize two separate widgets handling current project selection.
+
+ It is easier to trigger project change callbacks from one place than from
+ multiple differect places without proper handling or sequence changes.
+
+ Args:
+ dbcon(AvalonMongoDB): Mongo connection with Session.
+ model(ProjectModel): Object of projects model which is shared across
+ all widgets using projects. Arg dbcon should be used as source for
+ the model.
+ """
+ # Project list will be refreshed each 10000 msecs
+ # - this is not part of helper implementation but should be used by widgets
+ # that may require reshing of projects
+ refresh_interval = 10000
+
+ # Signal emmited when project has changed
+ project_changed = QtCore.Signal(str)
+
+ def __init__(self, dbcon, model):
+ super(ProjectHandler, self).__init__()
+ # Store project model for usage
+ self.model = model
+ # Store dbcon
+ self.dbcon = dbcon
+
+ self.current_project = dbcon.Session.get("AVALON_PROJECT")
+
+ def set_project(self, project_name):
+ # Change current project of this handler
+ self.current_project = project_name
+ # Change session project to take effect for other widgets using the
+ # dbcon object.
+ self.dbcon.Session["AVALON_PROJECT"] = project_name
+
+ # Trigger change signal when everything is updated to new project
+ self.project_changed.emit(project_name)
+
+ def refresh_model(self):
+ self.model.refresh()
+
+
def get_action_icon(action):
icon_name = action.icon
if not icon_name:
diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py
index 0e8caeb278..048210115c 100644
--- a/openpype/tools/launcher/widgets.py
+++ b/openpype/tools/launcher/widgets.py
@@ -20,25 +20,15 @@ from .constants import (
class ProjectBar(QtWidgets.QWidget):
- project_changed = QtCore.Signal(int)
-
- # Project list will be refreshed each 10000 msecs
- refresh_interval = 10000
-
- def __init__(self, dbcon, parent=None):
+ def __init__(self, project_handler, parent=None):
super(ProjectBar, self).__init__(parent)
- self.dbcon = dbcon
-
- model = ProjectModel(dbcon)
- model.hide_invisible = True
-
project_combobox = QtWidgets.QComboBox(self)
# Change delegate so stylysheets are applied
project_delegate = QtWidgets.QStyledItemDelegate(project_combobox)
project_combobox.setItemDelegate(project_delegate)
- project_combobox.setModel(model)
+ project_combobox.setModel(project_handler.model)
project_combobox.setRootModelIndex(QtCore.QModelIndex())
layout = QtWidgets.QHBoxLayout(self)
@@ -51,19 +41,20 @@ class ProjectBar(QtWidgets.QWidget):
)
refresh_timer = QtCore.QTimer()
- refresh_timer.setInterval(self.refresh_interval)
+ refresh_timer.setInterval(project_handler.refresh_interval)
- self.model = model
+ self.project_handler = project_handler
self.project_delegate = project_delegate
self.project_combobox = project_combobox
self.refresh_timer = refresh_timer
# Signals
refresh_timer.timeout.connect(self._on_refresh_timeout)
- self.project_combobox.currentIndexChanged.connect(self.project_changed)
+ self.project_combobox.currentIndexChanged.connect(self.on_index_change)
+ project_handler.project_changed.connect(self._on_project_change)
# Set current project by default if it's set.
- project_name = self.dbcon.Session.get("AVALON_PROJECT")
+ project_name = project_handler.current_project
if project_name:
self.set_project(project_name)
@@ -79,7 +70,12 @@ class ProjectBar(QtWidgets.QWidget):
elif self.isActiveWindow():
# Refresh projects if window is active
- self.model.refresh()
+ self.project_handler.refresh_model()
+
+ def _on_project_change(self, project_name):
+ if self.get_current_project() == project_name:
+ return
+ self.set_project(project_name)
def get_current_project(self):
return self.project_combobox.currentText()
@@ -88,14 +84,18 @@ class ProjectBar(QtWidgets.QWidget):
index = self.project_combobox.findText(project_name)
if index < 0:
# Try refresh combobox model
- self.refresh()
+ self.project_handler.refresh_model()
index = self.project_combobox.findText(project_name)
if index >= 0:
self.project_combobox.setCurrentIndex(index)
- def refresh(self):
- self.model.refresh()
+ def on_index_change(self, idx):
+ if not self.isVisible():
+ return
+
+ project_name = self.get_current_project()
+ self.project_handler.set_project(project_name)
class ActionBar(QtWidgets.QWidget):
diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py
index a6d34bbe9d..979aab42cf 100644
--- a/openpype/tools/launcher/window.py
+++ b/openpype/tools/launcher/window.py
@@ -12,7 +12,7 @@ from avalon.tools import lib as tools_lib
from avalon.tools.widgets import AssetWidget
from avalon.vendor import qtawesome
from .models import ProjectModel
-from .lib import get_action_label
+from .lib import get_action_label, ProjectHandler
from .widgets import (
ProjectBar,
ActionBar,
@@ -89,46 +89,37 @@ class ProjectIconView(QtWidgets.QListView):
class ProjectsPanel(QtWidgets.QWidget):
"""Projects Page"""
-
- project_clicked = QtCore.Signal(str)
- # Refresh projects each 10000 msecs
- refresh_interval = 10000
-
- def __init__(self, dbcon, parent=None):
+ def __init__(self, project_handler, parent=None):
super(ProjectsPanel, self).__init__(parent=parent)
layout = QtWidgets.QVBoxLayout(self)
- self.dbcon = dbcon
- self.dbcon.install()
-
view = ProjectIconView(parent=self)
view.setSelectionMode(QtWidgets.QListView.NoSelection)
flick = FlickCharm(parent=self)
flick.activateOn(view)
- model = ProjectModel(self.dbcon)
- model.hide_invisible = True
- view.setModel(model)
+
+ view.setModel(project_handler.model)
layout.addWidget(view)
refresh_timer = QtCore.QTimer()
- refresh_timer.setInterval(self.refresh_interval)
+ refresh_timer.setInterval(project_handler.refresh_interval)
refresh_timer.timeout.connect(self._on_refresh_timeout)
view.clicked.connect(self.on_clicked)
- self.model = model
self.view = view
self.refresh_timer = refresh_timer
+ self.project_handler = project_handler
def on_clicked(self, index):
if index.isValid():
project_name = index.data(QtCore.Qt.DisplayRole)
- self.project_clicked.emit(project_name)
+ self.project_handler.set_project(project_name)
def showEvent(self, event):
- self.model.refresh()
+ self.project_handler.refresh_model()
if not self.refresh_timer.isActive():
self.refresh_timer.start()
super(ProjectsPanel, self).showEvent(event)
@@ -140,7 +131,7 @@ class ProjectsPanel(QtWidgets.QWidget):
elif self.isActiveWindow():
# Refresh projects if window is active
- self.model.refresh()
+ self.project_handler.refresh_model()
class AssetsPanel(QtWidgets.QWidget):
@@ -148,7 +139,7 @@ class AssetsPanel(QtWidgets.QWidget):
back_clicked = QtCore.Signal()
session_changed = QtCore.Signal()
- def __init__(self, dbcon, parent=None):
+ def __init__(self, project_handler, dbcon, parent=None):
super(AssetsPanel, self).__init__(parent=parent)
self.dbcon = dbcon
@@ -163,7 +154,7 @@ class AssetsPanel(QtWidgets.QWidget):
btn_back = QtWidgets.QPushButton(project_bar_widget)
btn_back.setIcon(btn_back_icon)
- project_bar = ProjectBar(self.dbcon, project_bar_widget)
+ project_bar = ProjectBar(project_handler, project_bar_widget)
layout.addWidget(btn_back)
layout.addWidget(project_bar)
@@ -206,24 +197,19 @@ class AssetsPanel(QtWidgets.QWidget):
layout.addWidget(body)
# signals
- project_bar.project_changed.connect(self.on_project_changed)
+ project_handler.project_changed.connect(self.on_project_changed)
assets_widget.selection_changed.connect(self.on_asset_changed)
assets_widget.refreshed.connect(self.on_asset_changed)
tasks_widget.task_changed.connect(self.on_task_change)
btn_back.clicked.connect(self.back_clicked)
+ self.project_handler = project_handler
self.project_bar = project_bar
self.assets_widget = assets_widget
self.tasks_widget = tasks_widget
self._btn_back = btn_back
- # Force initial refresh for the assets since we might not be
- # trigging a Project switch if we click the project that was set
- # prior to launching the Launcher
- # todo: remove this behavior when AVALON_PROJECT is not required
- assets_widget.refresh()
-
def showEvent(self, event):
super(AssetsPanel, self).showEvent(event)
@@ -232,19 +218,7 @@ class AssetsPanel(QtWidgets.QWidget):
btn_size = self.project_bar.height()
self._btn_back.setFixedSize(QtCore.QSize(btn_size, btn_size))
- def set_project(self, project):
- before = self.project_bar.get_current_project()
- if before == project:
- self.assets_widget.refresh()
- return
-
- self.project_bar.set_project(project)
- self.on_project_changed()
-
def on_project_changed(self):
- project_name = self.project_bar.get_current_project()
- self.dbcon.Session["AVALON_PROJECT"] = project_name
-
self.session_changed.emit()
self.assets_widget.refresh()
@@ -253,11 +227,8 @@ class AssetsPanel(QtWidgets.QWidget):
"""Callback on asset selection changed
This updates the task view.
-
"""
- print("Asset changed..")
-
asset_name = None
asset_silo = None
@@ -321,8 +292,12 @@ class LauncherWindow(QtWidgets.QDialog):
self.windowFlags() | QtCore.Qt.WindowMinimizeButtonHint
)
- project_panel = ProjectsPanel(self.dbcon)
- asset_panel = AssetsPanel(self.dbcon)
+ project_model = ProjectModel(self.dbcon)
+ project_model.hide_invisible = True
+ project_handler = ProjectHandler(self.dbcon, project_model)
+
+ project_panel = ProjectsPanel(project_handler)
+ asset_panel = AssetsPanel(project_handler, self.dbcon)
page_slider = SlidePageWidget()
page_slider.addWidget(project_panel)
@@ -371,6 +346,8 @@ class LauncherWindow(QtWidgets.QDialog):
actions_refresh_timer.setInterval(self.actions_refresh_timeout)
self.actions_refresh_timer = actions_refresh_timer
+ self.project_handler = project_handler
+
self.message_label = message_label
self.project_panel = project_panel
self.asset_panel = asset_panel
@@ -383,15 +360,10 @@ class LauncherWindow(QtWidgets.QDialog):
actions_refresh_timer.timeout.connect(self._on_action_timer)
actions_bar.action_clicked.connect(self.on_action_clicked)
action_history.trigger_history.connect(self.on_history_action)
- project_panel.project_clicked.connect(self.on_project_clicked)
+ project_handler.project_changed.connect(self.on_project_change)
asset_panel.back_clicked.connect(self.on_back_clicked)
asset_panel.session_changed.connect(self.on_session_changed)
- # todo: Simplify this callback connection
- asset_panel.project_bar.project_changed.connect(
- self.on_project_changed
- )
-
self.resize(520, 740)
def showEvent(self, event):
@@ -415,13 +387,6 @@ class LauncherWindow(QtWidgets.QDialog):
QtCore.QTimer.singleShot(5000, lambda: self.message_label.setText(""))
self.log.debug(message)
- def on_project_changed(self):
- project_name = self.asset_panel.project_bar.get_current_project()
- self.dbcon.Session["AVALON_PROJECT"] = project_name
-
- # Update the Action plug-ins available for the current project
- self.discover_actions()
-
def on_session_changed(self):
self.filter_actions()
@@ -441,15 +406,13 @@ class LauncherWindow(QtWidgets.QDialog):
# Refresh projects if window is active
self.discover_actions()
- def on_project_clicked(self, project_name):
- self.dbcon.Session["AVALON_PROJECT"] = project_name
- # Refresh projects
- self.asset_panel.set_project(project_name)
+ def on_project_change(self, project_name):
+ # Update the Action plug-ins available for the current project
self.set_page(1)
self.discover_actions()
def on_back_clicked(self):
- self.dbcon.Session["AVALON_PROJECT"] = None
+ self.project_handler.set_project(None)
self.set_page(0)
self.discover_actions()
diff --git a/openpype/tools/settings/local_settings/apps_widget.py b/openpype/tools/settings/local_settings/apps_widget.py
index 5f4e5dd1c5..e6a4132955 100644
--- a/openpype/tools/settings/local_settings/apps_widget.py
+++ b/openpype/tools/settings/local_settings/apps_widget.py
@@ -121,6 +121,9 @@ class AppGroupWidget(QtWidgets.QWidget):
widgets_by_variant_name = {}
for variant_name, variant_entity in valid_variants.items():
+ if "executables" not in variant_entity:
+ continue
+
variant_widget = AppVariantWidget(
group_label, variant_name, variant_entity, content_widget
)
@@ -193,8 +196,12 @@ class LocalApplicationsWidgets(QtWidgets.QWidget):
# Create App group specific widget and store it by the key
group_widget = AppGroupWidget(entity, self)
- self.widgets_by_group_name[key] = group_widget
- self.content_layout.addWidget(group_widget)
+ if group_widget.widgets_by_variant_name:
+ self.widgets_by_group_name[key] = group_widget
+ self.content_layout.addWidget(group_widget)
+ else:
+ group_widget.setVisible(False)
+ group_widget.deleteLater()
def update_local_settings(self, value):
if not value:
diff --git a/openpype/tools/settings/local_settings/window.py b/openpype/tools/settings/local_settings/window.py
index 69562d0b1f..9e8fd89b23 100644
--- a/openpype/tools/settings/local_settings/window.py
+++ b/openpype/tools/settings/local_settings/window.py
@@ -168,9 +168,6 @@ class LocalSettingsWindow(QtWidgets.QWidget):
scroll_widget = QtWidgets.QScrollArea(self)
scroll_widget.setObjectName("GroupWidget")
- settings_widget = LocalSettingsWidget(scroll_widget)
-
- scroll_widget.setWidget(settings_widget)
scroll_widget.setWidgetResizable(True)
footer = QtWidgets.QWidget(self)
@@ -191,7 +188,12 @@ class LocalSettingsWindow(QtWidgets.QWidget):
save_btn.clicked.connect(self._on_save_clicked)
reset_btn.clicked.connect(self._on_reset_clicked)
- self.settings_widget = settings_widget
+ # Do not create local settings widget in init phase as it's using
+ # settings objects that must be OK to be able create this widget
+ # - we want to show dialog if anything goes wrong
+ # - without reseting nothing is shown
+ self._settings_widget = None
+ self._scroll_widget = scroll_widget
self.reset_btn = reset_btn
self.save_btn = save_btn
@@ -203,13 +205,53 @@ class LocalSettingsWindow(QtWidgets.QWidget):
def reset(self):
if self._reset_on_show:
self._reset_on_show = False
- value = get_local_settings()
- self.settings_widget.update_local_settings(value)
+
+ error_msg = None
+ try:
+ # Create settings widget if is not created yet
+ if self._settings_widget is None:
+ self._settings_widget = LocalSettingsWidget(
+ self._scroll_widget
+ )
+ self._scroll_widget.setWidget(self._settings_widget)
+
+ value = get_local_settings()
+ self._settings_widget.update_local_settings(value)
+
+ except Exception as exc:
+ error_msg = str(exc)
+
+ crashed = error_msg is not None
+ # Enable/Disable save button if crashed or not
+ self.save_btn.setEnabled(not crashed)
+ # Show/Hide settings widget if crashed or not
+ if self._settings_widget:
+ self._settings_widget.setVisible(not crashed)
+
+ if not crashed:
+ return
+
+ # Show message with error
+ title = "Something went wrong"
+ msg = (
+ "Bug: Loading of settings failed."
+ " Please contact your project manager or OpenPype team."
+ "\n\nError message:\n{}"
+ ).format(error_msg)
+
+ dialog = QtWidgets.QMessageBox(
+ QtWidgets.QMessageBox.Critical,
+ title,
+ msg,
+ QtWidgets.QMessageBox.Ok,
+ self
+ )
+ dialog.exec_()
def _on_reset_clicked(self):
self.reset()
def _on_save_clicked(self):
- value = self.settings_widget.settings_value()
+ value = self._settings_widget.settings_value()
save_local_settings(value)
self.reset()