Merge pull request #2043 from pypeclub/feature/loader_families_filtering

Loader: Families filtering
This commit is contained in:
Jakub Trllo 2021-09-21 14:13:47 +02:00 committed by GitHub
commit 454fbca065
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 421 additions and 198 deletions

View file

@ -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]\": {}}}}",

View file

@ -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
}
}
]
}
}
]
}
]
}

View file

@ -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"}
]
}
]

View file

@ -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"]

View file

@ -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"]

View file

@ -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

View file

@ -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):

View file

@ -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: