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()