diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 05c3e871c0..8cc8d28e5f 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -297,6 +297,15 @@ "textures" ] } + }, + "loader": { + "family_filter_profiles": [ + { + "hosts": [], + "task_types": [], + "filter_families": [] + } + ] } }, "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets[ftrack.Library]\": {\"characters[ftrack]\": {}, \"locations[ftrack]\": {}}, \"shots[ftrack.Sequence]\": {\"scripts\": {}, \"editorial[ftrack.Folder]\": {}}}}", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index e6f9bc41a5..26d3771d8a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -206,6 +206,48 @@ } } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "loader", + "label": "Loader", + "children": [ + { + "type": "list", + "key": "family_filter_profiles", + "label": "Family filtering", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "type": "task-types-enum", + "key": "task_types", + "label": "Task types" + }, + { + "type": "splitter" + }, + { + "type": "template", + "name": "template_publish_families", + "template_data": { + "key": "filter_families", + "label": "Filter families", + "multiselection": true + } + } + ] + } + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_publish_families.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_publish_families.json new file mode 100644 index 0000000000..9db1427562 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_publish_families.json @@ -0,0 +1,32 @@ +[ + { + "__default_values__": { + "multiselection": true + } + }, + { + "key": "{key}", + "label": "{label}", + "multiselection": "{multiselection}", + "type": "enum", + "enum_items": [ + {"action": "action"}, + {"animation": "animation"}, + {"audio": "audio"}, + {"camera": "camera"}, + {"editorial": "editorial"}, + {"layout": "layout"}, + {"look": "look"}, + {"mayaAscii": "mayaAscii"}, + {"model": "model"}, + {"pointcache": "pointcache"}, + {"reference": "reference"}, + {"render": "render"}, + {"review": "review"}, + {"rig": "rig"}, + {"setdress": "setdress"}, + {"workfile": "workfile"}, + {"xgen": "xgen"} + ] + } +] diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 362d05cce6..8080c547c9 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -1,5 +1,4 @@ import sys -import time from Qt import QtWidgets, QtCore, QtGui @@ -9,7 +8,7 @@ from openpype.tools.utils import lib as tools_lib from openpype.tools.loader.widgets import ( ThumbnailWidget, VersionWidget, - FamilyListWidget, + FamilyListView, RepresentationWidget ) from openpype.tools.utils.widgets import AssetWidget @@ -65,7 +64,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): assets = AssetWidget( self.dbcon, multiselection=True, parent=self ) - families = FamilyListWidget( + families = FamilyListView( self.dbcon, self.family_config_cache, parent=self ) subsets = LibrarySubsetWidget( @@ -151,6 +150,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): assets.view.clicked.connect(self.on_assetview_click) subsets.active_changed.connect(self.on_subsetschanged) subsets.version_changed.connect(self.on_versionschanged) + subsets.refreshed.connect(self._on_subset_refresh) self.combo_projects.currentTextChanged.connect(self.on_project_change) self.sync_server = sync_server @@ -242,6 +242,12 @@ class LibraryLoaderWindow(QtWidgets.QDialog): "Config `%s` has no function `install`" % _config.__name__ ) + subsets = self.data["widgets"]["subsets"] + representations = self.data["widgets"]["representations"] + + subsets.on_project_change(self.dbcon.Session["AVALON_PROJECT"]) + representations.on_project_change(self.dbcon.Session["AVALON_PROJECT"]) + self.family_config_cache.refresh() self.groups_config.refresh() @@ -252,12 +258,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog): title = "{} - {}".format(self.tool_title, project_name) self.setWindowTitle(title) - subsets = self.data["widgets"]["subsets"] - subsets.on_project_change(self.dbcon.Session["AVALON_PROJECT"]) - - representations = self.data["widgets"]["representations"] - representations.on_project_change(self.dbcon.Session["AVALON_PROJECT"]) - @property def current_project(self): if ( @@ -288,6 +288,14 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self.echo("Fetching version..") tools_lib.schedule(self._versionschanged, 150, channel="mongo") + def _on_subset_refresh(self, has_item): + subsets_widget = self.data["widgets"]["subsets"] + families_view = self.data["widgets"]["families"] + + subsets_widget.set_loading_state(loading=False, empty=not has_item) + families = subsets_widget.get_subsets_families() + families_view.set_enabled_families(families) + def set_context(self, context, refresh=True): self.echo("Setting context: {}".format(context)) lib.schedule( @@ -312,13 +320,14 @@ class LibraryLoaderWindow(QtWidgets.QDialog): assert project_doc, "This is a bug" assets_widget = self.data["widgets"]["assets"] + families_view = self.data["widgets"]["families"] + families_view.set_enabled_families(set()) + families_view.refresh() + assets_widget.model.stop_fetch_thread() assets_widget.refresh() assets_widget.setFocus() - families = self.data["widgets"]["families"] - families.refresh() - def clear_assets_underlines(self): last_asset_ids = self.data["state"]["assetIds"] if not last_asset_ids: @@ -337,8 +346,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog): def _assetschanged(self): """Selected assets have changed""" - t1 = time.time() - assets_widget = self.data["widgets"]["assets"] subsets_widget = self.data["widgets"]["subsets"] subsets_model = subsets_widget.model @@ -365,14 +372,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog): empty=True ) - def on_refreshed(has_item): - empty = not has_item - subsets_widget.set_loading_state(loading=False, empty=empty) - subsets_model.refreshed.disconnect() - self.echo("Duration: %.3fs" % (time.time() - t1)) - - subsets_model.refreshed.connect(on_refreshed) - subsets_model.set_assets(asset_ids) subsets_widget.view.setColumnHidden( subsets_model.Columns.index("asset"), @@ -386,9 +385,8 @@ class LibraryLoaderWindow(QtWidgets.QDialog): self.data["state"]["assetIds"] = asset_ids representations = self.data["widgets"]["representations"] - representations.set_version_ids([]) # reset repre list - - self.echo("Duration: %.3fs" % (time.time() - t1)) + # reset repre list + representations.set_version_ids([]) def _subsetschanged(self): asset_ids = self.data["state"]["assetIds"] diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 5db7a3bcb1..c18b6e798a 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -1,5 +1,4 @@ import sys -import time from Qt import QtWidgets, QtCore from avalon import api, io, style, pipeline @@ -11,7 +10,7 @@ from openpype.tools.utils import lib from .widgets import ( SubsetWidget, VersionWidget, - FamilyListWidget, + FamilyListView, ThumbnailWidget, RepresentationWidget, OverlayFrame @@ -64,7 +63,7 @@ class LoaderWindow(QtWidgets.QDialog): assets = AssetWidget(io, multiselection=True, parent=self) assets.set_current_asset_btn_visibility(True) - families = FamilyListWidget(io, self.family_config_cache, self) + families = FamilyListView(io, self.family_config_cache, self) subsets = SubsetWidget( io, self.groups_config, @@ -146,6 +145,7 @@ class LoaderWindow(QtWidgets.QDialog): assets.view.clicked.connect(self.on_assetview_click) subsets.active_changed.connect(self.on_subsetschanged) subsets.version_changed.connect(self.on_versionschanged) + subsets.refreshed.connect(self._on_subset_refresh) subsets.load_started.connect(self._on_load_start) subsets.load_ended.connect(self._on_load_end) @@ -215,6 +215,14 @@ class LoaderWindow(QtWidgets.QDialog): def _hide_overlay(self): self._overlay_frame.setVisible(False) + def _on_subset_refresh(self, has_item): + subsets_widget = self.data["widgets"]["subsets"] + families_view = self.data["widgets"]["families"] + + subsets_widget.set_loading_state(loading=False, empty=not has_item) + families = subsets_widget.get_subsets_families() + families_view.set_enabled_families(families) + def _on_load_end(self): # Delay hiding as click events happened during loading should be # blocked @@ -223,8 +231,11 @@ class LoaderWindow(QtWidgets.QDialog): # ------------------------------ def on_context_task_change(self, *args, **kwargs): - # Change to context asset on context change assets_widget = self.data["widgets"]["assets"] + families_view = self.data["widgets"]["families"] + # Refresh families config + families_view.refresh() + # Change to context asset on context change assets_widget.select_assets(io.Session["AVALON_ASSET"]) def _refresh(self): @@ -238,8 +249,8 @@ class LoaderWindow(QtWidgets.QDialog): assets_widget.refresh() assets_widget.setFocus() - families = self.data["widgets"]["families"] - families.refresh() + families_view = self.data["widgets"]["families"] + families_view.refresh() def clear_assets_underlines(self): """Clear colors from asset data to remove colored underlines @@ -264,8 +275,6 @@ class LoaderWindow(QtWidgets.QDialog): def _assetschanged(self): """Selected assets have changed""" - t1 = time.time() - assets_widget = self.data["widgets"]["assets"] subsets_widget = self.data["widgets"]["subsets"] subsets_model = subsets_widget.model @@ -283,14 +292,6 @@ class LoaderWindow(QtWidgets.QDialog): empty=True ) - def on_refreshed(has_item): - empty = not has_item - subsets_widget.set_loading_state(loading=False, empty=empty) - subsets_model.refreshed.disconnect() - self.echo("Duration: %.3fs" % (time.time() - t1)) - - subsets_model.refreshed.connect(on_refreshed) - subsets_model.set_assets(asset_ids) subsets_widget.view.setColumnHidden( subsets_model.Columns.index("asset"), @@ -304,7 +305,8 @@ class LoaderWindow(QtWidgets.QDialog): self.data["state"]["assetIds"] = asset_ids representations = self.data["widgets"]["representations"] - representations.set_version_ids([]) # reset repre list + # reset repre list + representations.set_version_ids([]) def _subsetschanged(self): asset_ids = self.data["state"]["assetIds"] diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 253341f70d..6e9c7bf220 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -70,7 +70,6 @@ class BaseRepresentationModel(object): class SubsetsModel(TreeModel, BaseRepresentationModel): - doc_fetched = QtCore.Signal() refreshed = QtCore.Signal(bool) @@ -128,7 +127,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "name": 1, "parent": 1, "schema": 1, - "families": 1, + "data.families": 1, "data.subsetGroup": 1 } @@ -191,6 +190,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._grouping = state self.on_doc_fetched() + def get_subsets_families(self): + return self._doc_payload.get("subset_families") or set() + def setData(self, index, value, role=QtCore.Qt.EditRole): # Trigger additional edit when `version` column changed # because it also updates the information in other columns @@ -354,10 +356,16 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): }, self.subset_doc_projection ) - for subset in subset_docs: + subset_families = set() + for subset_doc in subset_docs: if self._doc_fetching_stop: return - subset_docs_by_id[subset["_id"]] = subset + + families = subset_doc.get("data", {}).get("families") + if families: + subset_families.add(families[0]) + + subset_docs_by_id[subset_doc["_id"]] = subset_doc subset_ids = list(subset_docs_by_id.keys()) _pipeline = [ @@ -428,6 +436,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._doc_payload = { "asset_docs_by_id": asset_docs_by_id, "subset_docs_by_id": subset_docs_by_id, + "subset_families": subset_families, "last_versions_by_subset_id": last_versions_by_subset_id } @@ -851,10 +860,9 @@ class SubsetFilterProxyModel(GroupMemberFilterProxyModel): class FamiliesFilterProxyModel(GroupMemberFilterProxyModel): """Filters to specified families""" - def __init__(self, family_config_cache, *args, **kwargs): + def __init__(self, *args, **kwargs): super(FamiliesFilterProxyModel, self).__init__(*args, **kwargs) self._families = set() - self.family_config_cache = family_config_cache def familyFilter(self): return self._families @@ -886,10 +894,6 @@ class FamiliesFilterProxyModel(GroupMemberFilterProxyModel): if not family: return True - family_config = self.family_config_cache.family_config(family) - if family_config.get("hideFilter"): - return False - # We want to keep the families which are not in the list return family in self._families diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 39d162613a..6b94fc6e44 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -122,6 +122,7 @@ class SubsetWidget(QtWidgets.QWidget): version_changed = QtCore.Signal() # version state changed for a subset load_started = QtCore.Signal() load_ended = QtCore.Signal() + refreshed = QtCore.Signal(bool) default_widths = ( ("subset", 200), @@ -158,7 +159,7 @@ class SubsetWidget(QtWidgets.QWidget): grouping=enable_grouping ) proxy = SubsetFilterProxyModel() - family_proxy = FamiliesFilterProxyModel(family_config_cache) + family_proxy = FamiliesFilterProxyModel() family_proxy.setSourceModel(proxy) subset_filter = QtWidgets.QLineEdit() @@ -242,9 +243,13 @@ class SubsetWidget(QtWidgets.QWidget): self.filter.textChanged.connect(self.proxy.setFilterRegExp) self.filter.textChanged.connect(self.view.expandAll) + model.refreshed.connect(self.refreshed) self.model.refresh() + def get_subsets_families(self): + return self.model.get_subsets_families() + def set_family_filters(self, families): self.family_proxy.setFamiliesFilter(families) @@ -846,36 +851,17 @@ class VersionWidget(QtWidgets.QWidget): self.data.set_version(version_doc) -class FamilyListWidget(QtWidgets.QListWidget): - """A Widget that lists all available families""" +class FamilyModel(QtGui.QStandardItemModel): + def __init__(self, dbcon, family_config_cache): + super(FamilyModel, self).__init__() - NameRole = QtCore.Qt.UserRole + 1 - active_changed = QtCore.Signal(list) - - def __init__(self, dbcon, family_config_cache, parent=None): - super(FamilyListWidget, self).__init__(parent=parent) - - self.family_config_cache = family_config_cache self.dbcon = dbcon + self.family_config_cache = family_config_cache - multi_select = QtWidgets.QAbstractItemView.ExtendedSelection - self.setSelectionMode(multi_select) - self.setAlternatingRowColors(True) - # Enable RMB menu - self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.customContextMenuRequested.connect(self.show_right_mouse_menu) - - self.itemChanged.connect(self._on_item_changed) + self._items_by_family = {} def refresh(self): - """Refresh the listed families. - - This gets all unique families and adds them as checkable items to - the list. - - """ - - families = [] + families = set() if self.dbcon.Session.get("AVALON_PROJECT"): result = list(self.dbcon.aggregate([ {"$match": { @@ -890,81 +876,228 @@ class FamilyListWidget(QtWidgets.QListWidget): }} ])) if result: - families = result[0]["families"] + families = set(result[0]["families"]) - # Rebuild list - self.blockSignals(True) - self.clear() - for name in sorted(families): - family = self.family_config_cache.family_config(name) - if family.get("hideFilter"): - continue + root_item = self.invisibleRootItem() - label = family.get("label", name) - icon = family.get("icon", None) + for family in tuple(self._items_by_family.keys()): + if family not in families: + item = self._items_by_family.pop(family) + root_item.removeRow(item.row()) - # TODO: This should be more managable by the artist - # Temporarily implement support for a default state in the project - # configuration - state = family.get("state", True) - state = QtCore.Qt.Checked if state else QtCore.Qt.Unchecked + self.family_config_cache.refresh() + + new_items = [] + for family in families: + family_config = self.family_config_cache.family_config(family) + label = family_config.get("label", family) + icon = family_config.get("icon", None) + + if family_config.get("state", True): + state = QtCore.Qt.Checked + else: + state = QtCore.Qt.Unchecked + + if family not in self._items_by_family: + item = QtGui.QStandardItem(label) + item.setFlags( + QtCore.Qt.ItemIsEnabled + | QtCore.Qt.ItemIsSelectable + | QtCore.Qt.ItemIsUserCheckable + ) + new_items.append(item) + self._items_by_family[family] = item + + else: + item = self._items_by_family[label] + item.setData(label, QtCore.Qt.DisplayRole) - item = QtWidgets.QListWidgetItem(parent=self) - item.setText(label) - item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) - item.setData(self.NameRole, name) item.setCheckState(state) if icon: item.setIcon(icon) - self.addItem(item) - self.blockSignals(False) + if new_items: + root_item.appendRows(new_items) - self.active_changed.emit(self.get_filters()) - def get_filters(self): +class FamilyProxyFiler(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(FamilyProxyFiler, self).__init__(*args, **kwargs) + + self._filtering_enabled = False + self._enabled_families = set() + + def set_enabled_families(self, families): + if self._enabled_families == families: + return + + self._enabled_families = families + if self._filtering_enabled: + self.invalidateFilter() + + def is_filter_enabled(self): + return self._filtering_enabled + + def set_filter_enabled(self, enabled=None): + if enabled is None: + enabled = not self._filtering_enabled + elif self._filtering_enabled == enabled: + return + + self._filtering_enabled = enabled + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + if not self._filtering_enabled: + return True + + if not self._enabled_families: + return False + + index = self.sourceModel().index(row, self.filterKeyColumn(), parent) + if index.data(QtCore.Qt.DisplayRole) in self._enabled_families: + return True + return False + + +class FamilyListView(QtWidgets.QListView): + active_changed = QtCore.Signal(list) + + def __init__(self, dbcon, family_config_cache, parent=None): + super(FamilyListView, self).__init__(parent=parent) + + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.setAlternatingRowColors(True) + self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + + family_model = FamilyModel(dbcon, family_config_cache) + proxy_model = FamilyProxyFiler() + proxy_model.setDynamicSortFilter(True) + proxy_model.setSourceModel(family_model) + + self.setModel(proxy_model) + + family_model.dataChanged.connect(self._on_data_change) + self.customContextMenuRequested.connect(self._on_context_menu) + + self._family_model = family_model + self._proxy_model = proxy_model + + def set_enabled_families(self, families): + self._proxy_model.set_enabled_families(families) + + self.set_enabled_family_filtering(True) + + def set_enabled_family_filtering(self, enabled=None): + self._proxy_model.set_filter_enabled(enabled) + + def refresh(self): + self._family_model.refresh() + + self.active_changed.emit(self.get_enabled_families()) + + def get_enabled_families(self): """Return the checked family items""" + model = self._family_model + checked_families = [] + for row in range(model.rowCount()): + index = model.index(row, 0) + if index.data(QtCore.Qt.CheckStateRole) == QtCore.Qt.Checked: + family = index.data(QtCore.Qt.DisplayRole) + checked_families.append(family) - items = [self.item(i) for i in - range(self.count())] + return checked_families - return [item.data(self.NameRole) for item in items if - item.checkState() == QtCore.Qt.Checked] + def set_all_unchecked(self): + self._set_checkstates(False, self._get_all_indexes()) - def _on_item_changed(self): - self.active_changed.emit(self.get_filters()) + def set_all_checked(self): + self._set_checkstates(True, self._get_all_indexes()) + + def _get_all_indexes(self): + indexes = [] + model = self._family_model + for row in range(model.rowCount()): + index = model.index(row, 0) + indexes.append(index) + return indexes + + def _set_checkstates(self, checked, indexes): + if not indexes: + return + + if checked is None: + state = None + elif checked: + state = QtCore.Qt.Checked + else: + state = QtCore.Qt.Unchecked - def _set_checkstate_all(self, state): - _state = QtCore.Qt.Checked if state is True else QtCore.Qt.Unchecked self.blockSignals(True) - for i in range(self.count()): - item = self.item(i) - item.setCheckState(_state) + + for index in indexes: + index_state = index.data(QtCore.Qt.CheckStateRole) + if index_state == state: + continue + + new_state = state + if new_state is None: + if index_state == QtCore.Qt.Checked: + new_state = QtCore.Qt.Unchecked + else: + new_state = QtCore.Qt.Checked + + index.model().setData(index, new_state, QtCore.Qt.CheckStateRole) + self.blockSignals(False) - self.active_changed.emit(self.get_filters()) - def show_right_mouse_menu(self, pos): + self.active_changed.emit(self.get_enabled_families()) + + def _change_selection_state(self, checked): + indexes = self.selectionModel().selectedIndexes() + self._set_checkstates(checked, indexes) + + def _on_data_change(self, *_args): + self.active_changed.emit(self.get_enabled_families()) + + def _on_context_menu(self, pos): """Build RMB menu under mouse at current position (within widget)""" - - # Get mouse position - globalpos = self.viewport().mapToGlobal(pos) - menu = QtWidgets.QMenu(self) # Add enable all action - state_checked = QtWidgets.QAction(menu, text="Enable All") - state_checked.triggered.connect( - lambda: self._set_checkstate_all(True)) + action_check_all = QtWidgets.QAction(menu) + action_check_all.setText("Enable All") + action_check_all.triggered.connect(self.set_all_checked) # Add disable all action - state_unchecked = QtWidgets.QAction(menu, text="Disable All") - state_unchecked.triggered.connect( - lambda: self._set_checkstate_all(False)) + action_uncheck_all = QtWidgets.QAction(menu) + action_uncheck_all.setText("Disable All") + action_uncheck_all.triggered.connect(self.set_all_unchecked) - menu.addAction(state_checked) - menu.addAction(state_unchecked) + menu.addAction(action_check_all) + menu.addAction(action_uncheck_all) - menu.exec_(globalpos) + # Get mouse position + global_pos = self.viewport().mapToGlobal(pos) + menu.exec_(global_pos) + + def event(self, event): + if not event.type() == QtCore.QEvent.KeyPress: + pass + + elif event.key() == QtCore.Qt.Key_Space: + self._change_selection_state(None) + return True + + elif event.key() == QtCore.Qt.Key_Backspace: + self._change_selection_state(False) + return True + + elif event.key() == QtCore.Qt.Key_Return: + self._change_selection_state(True) + return True + + return super(FamilyListView, self).event(event) class RepresentationWidget(QtWidgets.QWidget): diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index e83f663b2e..d01dbbd169 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -5,23 +5,12 @@ import collections from Qt import QtWidgets, QtCore, QtGui -from avalon import io, api, style +import avalon.api +from avalon import style from avalon.vendor import qtawesome -self = sys.modules[__name__] -self._jobs = dict() - - -class SharedObjects: - # Variable for family cache in global context - # QUESTION is this safe? More than one tool can refresh at the same time. - family_cache = None - - -def global_family_cache(): - if SharedObjects.family_cache is None: - SharedObjects.family_cache = FamilyConfigCache(io) - return SharedObjects.family_cache +from openpype.api import get_project_settings +from openpype.lib import filter_profiles def format_version(value, hero_version=False): @@ -66,6 +55,10 @@ def defer(delay, func): return func() +class SharedObjects: + jobs = {} + + def schedule(func, time, channel="default"): """Run `func` at a later `time` in a dedicated `channel` @@ -77,7 +70,7 @@ def schedule(func, time, channel="default"): """ try: - self._jobs[channel].stop() + SharedObjects.jobs[channel].stop() except (AttributeError, KeyError, RuntimeError): pass @@ -86,7 +79,7 @@ def schedule(func, time, channel="default"): timer.timeout.connect(func) timer.start(time) - self._jobs[channel] = timer + SharedObjects.jobs[channel] = timer @contextlib.contextmanager @@ -98,7 +91,6 @@ def dummy(): .. pass """ - yield @@ -289,11 +281,12 @@ def preserve_selection(tree_view, column=0, role=None, current_index=True): class FamilyConfigCache: default_color = "#0091B2" _default_icon = None - _default_item = None def __init__(self, dbcon): self.dbcon = dbcon self.family_configs = {} + self._family_filters_set = False + self._require_refresh = True @classmethod def default_icon(cls): @@ -303,17 +296,27 @@ class FamilyConfigCache: ) return cls._default_icon - @classmethod - def default_item(cls): - if cls._default_item is None: - cls._default_item = {"icon": cls.default_icon()} - return cls._default_item - def family_config(self, family_name): """Get value from config with fallback to default""" - return self.family_configs.get(family_name, self.default_item()) + if self._require_refresh: + self._refresh() - def refresh(self): + item = self.family_configs.get(family_name) + if not item: + item = { + "icon": self.default_icon() + } + if self._family_filters_set: + item["state"] = False + return item + + def refresh(self, force=False): + self._require_refresh = True + + if force: + self._refresh() + + def _refresh(self): """Get the family configurations from the database The configuration must be stored on the project under `config`. @@ -329,62 +332,62 @@ class FamilyConfigCache: It is possible to override the default behavior and set specific families checked. For example we only want the families imagesequence and camera to be visible in the Loader. - - # This will turn every item off - api.data["familyStateDefault"] = False - - # Only allow the imagesequence and camera - api.data["familyStateToggled"] = ["imagesequence", "camera"] - """ + self._require_refresh = False + self._family_filters_set = False self.family_configs.clear() - - families = [] + # Skip if we're not in host context + if not avalon.api.registered_host(): + return # Update the icons from the project configuration - project_name = self.dbcon.Session.get("AVALON_PROJECT") - if project_name: - project_doc = self.dbcon.find_one( - {"type": "project"}, - projection={"config.families": True} + project_name = os.environ.get("AVALON_PROJECT") + asset_name = os.environ.get("AVALON_ASSET") + task_name = os.environ.get("AVALON_TASK") + if not all((project_name, asset_name, task_name)): + return + + matching_item = None + project_settings = get_project_settings(project_name) + profiles = ( + project_settings + ["global"] + ["tools"] + ["loader"] + ["family_filter_profiles"] + ) + if profiles: + asset_doc = self.dbcon.find_one( + {"type": "asset", "name": asset_name}, + {"data.tasks": True} ) + tasks_info = asset_doc.get("data", {}).get("tasks") or {} + task_type = tasks_info.get(task_name, {}).get("type") + profiles_filter = { + "task_types": task_type, + "hosts": os.environ["AVALON_APP"] + } + matching_item = filter_profiles(profiles, profiles_filter) - if not project_doc: - print(( - "Project \"{}\" not found!" - " Can't refresh family icons cache." - ).format(project_name)) - else: - families = project_doc["config"].get("families") or [] + families = [] + if matching_item: + families = matching_item["filter_families"] - # Check if any family state are being overwritten by the configuration - default_state = api.data.get("familiesStateDefault", True) - toggled = set(api.data.get("familiesStateToggled") or []) + if not families: + return + + self._family_filters_set = True # Replace icons with a Qt icon we can use in the user interfaces for family in families: - name = family["name"] - # Set family icon - icon = family.get("icon", None) - if icon: - family["icon"] = qtawesome.icon( - "fa.{}".format(icon), - color=self.default_color - ) - else: - family["icon"] = self.default_icon() + family_info = { + "name": family, + "icon": self.default_icon(), + "state": True + } - # Update state - if name in toggled: - state = True - else: - state = default_state - family["state"] = state - - self.family_configs[name] = family - - return self.family_configs + self.family_configs[family] = family_info class GroupsConfig: