mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #2043 from pypeclub/feature/loader_families_filtering
Loader: Families filtering
This commit is contained in:
commit
454fbca065
8 changed files with 421 additions and 198 deletions
|
|
@ -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]\": {}}}}",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue