updated standalone publisher with changes in avalon-core

This commit is contained in:
iLLiCiTiT 2019-09-24 10:50:42 +02:00
parent bdc1ab1d0e
commit 3db98b88d6
8 changed files with 155 additions and 107 deletions

View file

@ -2,6 +2,7 @@ import os
import sys
import json
from subprocess import Popen
from bson.objectid import ObjectId
from pype import lib as pypelib
from avalon.vendor.Qt import QtWidgets, QtCore
from avalon import api, style, schema
@ -40,13 +41,13 @@ class Window(QtWidgets.QDialog):
self.valid_parent = False
# assets widget
widget_assets = AssetWidget(self)
widget_assets = AssetWidget(dbcon=self._db, parent=self)
# family widget
widget_family = FamilyWidget(self)
widget_family = FamilyWidget(dbcon=self._db, parent=self)
# components widget
widget_components = ComponentsWidget(self)
widget_components = ComponentsWidget(parent=self)
# Body
body = QtWidgets.QSplitter()
@ -70,6 +71,7 @@ class Window(QtWidgets.QDialog):
# signals
widget_assets.selection_changed.connect(self.on_asset_changed)
widget_family.stateChanged.connect(self.set_valid_family)
self.widget_assets = widget_assets
self.widget_family = widget_family
@ -123,7 +125,10 @@ class Window(QtWidgets.QDialog):
Updates the task view.
'''
selected = self.widget_assets.get_selected_assets()
selected = [
asset_id for asset_id in self.widget_assets.get_selected_assets()
if isinstance(asset_id, ObjectId)
]
if len(selected) == 1:
self.valid_parent = True
asset = self.db.find_one({"_id": selected[0], "type": "asset"})

View file

@ -1,5 +1,5 @@
from avalon.vendor.Qt import *
from avalon.vendor import qtawesome as awesome
from avalon.vendor import qtawesome
from avalon import style
HelpRole = QtCore.Qt.UserRole + 2
@ -12,13 +12,12 @@ from .button_from_svgs import SvgResizable, SvgButton
from .model_node import Node
from .model_tree import TreeModel
from .model_asset import AssetModel
from .model_asset import AssetModel, _iter_model_rows
from .model_filter_proxy_exact_match import ExactMatchesFilterProxyModel
from .model_filter_proxy_recursive_sort import RecursiveSortFilterProxyModel
from .model_tasks_template import TasksTemplateModel
from .model_tree_view_deselectable import DeselectableTreeView
from .widget_asset_view import AssetView
from .widget_asset import AssetWidget
from .widget_family_desc import FamilyDescriptionWidget

View file

@ -1,7 +1,8 @@
import logging
import collections
from . import QtCore, QtGui
from . import TreeModel, Node
from . import style, awesome
from . import style, qtawesome
log = logging.getLogger(__name__)
@ -44,64 +45,102 @@ class AssetModel(TreeModel):
DocumentRole = QtCore.Qt.UserRole + 2
ObjectIdRole = QtCore.Qt.UserRole + 3
def __init__(self, parent):
def __init__(self, dbcon, parent=None):
super(AssetModel, self).__init__(parent=parent)
self.parent_widget = parent
self.dbcon = dbcon
self.refresh()
@property
def db(self):
return self.parent_widget.db
def _add_hierarchy(self, assets, parent=None, silos=None):
"""Add the assets that are related to the parent as children items.
def _add_hierarchy(self, parent=None):
This method does *not* query the database. These instead are queried
in a single batch upfront as an optimization to reduce database
queries. Resulting in up to 10x speed increase.
# Find the assets under the parent
find_data = {
"type": "asset"
}
if parent is None:
find_data['$or'] = [
{'data.visualParent': {'$exists': False}},
{'data.visualParent': None}
]
else:
find_data["data.visualParent"] = parent['_id']
Args:
assets (dict): All assets in the currently active silo stored
by key/value
assets = self.db.find(find_data).sort('name', 1)
for asset in assets:
Returns:
None
"""
if silos:
# WARNING: Silo item "_id" is set to silo value
# mainly because GUI issue with perserve selection and expanded row
# and because of easier hierarchy parenting (in "assets")
for silo in silos:
node = Node({
"_id": silo,
"name": silo,
"label": silo,
"type": "silo"
})
self.add_child(node, parent=parent)
self._add_hierarchy(assets, parent=node)
parent_id = parent["_id"] if parent else None
current_assets = assets.get(parent_id, list())
for asset in current_assets:
# get label from data, otherwise use name
data = asset.get("data", {})
label = data.get("label", asset['name'])
label = data.get("label", asset["name"])
tags = data.get("tags", [])
# store for the asset for optimization
deprecated = "deprecated" in tags
node = Node({
"_id": asset['_id'],
"_id": asset["_id"],
"name": asset["name"],
"label": label,
"type": asset['type'],
"type": asset["type"],
"tags": ", ".join(tags),
"deprecated": deprecated,
"_document": asset
})
self.add_child(node, parent=parent)
# Add asset's children recursively
self._add_hierarchy(node)
# Add asset's children recursively if it has children
if asset["_id"] in assets:
self._add_hierarchy(assets, parent=node)
def refresh(self):
"""Refresh the data for the model."""
self.clear()
if (
self.db.active_project() is None or
self.db.active_project() == ''
self.dbcon.active_project() is None or
self.dbcon.active_project() == ''
):
return
self.beginResetModel()
self._add_hierarchy(parent=None)
# Get all assets in current silo sorted by name
db_assets = self.dbcon.find({"type": "asset"}).sort("name", 1)
silos = db_assets.distinct("silo") or None
# if any silo is set to None then it's expected it should not be used
if silos and None in silos:
silos = None
# Group the assets by their visual parent's id
assets_by_parent = collections.defaultdict(list)
for asset in db_assets:
parent_id = (
asset.get("data", {}).get("visualParent") or
asset.get("silo")
)
assets_by_parent[parent_id].append(asset)
# Build the hierarchical tree items recursively
self._add_hierarchy(
assets_by_parent,
parent=None,
silos=silos
)
self.endResetModel()
def flags(self, index):
@ -119,8 +158,10 @@ class AssetModel(TreeModel):
if column == self.Name:
# Allow a custom icon and custom icon color to be defined
data = node["_document"]["data"]
data = node.get("_document", {}).get("data", {})
icon = data.get("icon", None)
if icon is None and node.get("type") == "silo":
icon = "database"
color = data.get("color", style.colors.default)
if icon is None:
@ -136,7 +177,7 @@ class AssetModel(TreeModel):
try:
key = "fa.{0}".format(icon) # font-awesome key
icon = awesome.icon(key, color=color)
icon = qtawesome.icon(key, color=color)
return icon
except Exception as exception:
# Log an error message instead of erroring out completely

View file

@ -1,6 +1,6 @@
from . import QtCore, TreeModel
from . import Node
from . import awesome, style
from . import qtawesome, style
class TasksTemplateModel(TreeModel):
@ -11,7 +11,7 @@ class TasksTemplateModel(TreeModel):
def __init__(self, selectable=True):
super(TasksTemplateModel, self).__init__()
self.selectable = selectable
self.icon = awesome.icon(
self.icon = qtawesome.icon(
'fa.calendar-check-o',
color=style.colors.default
)

View file

@ -1,9 +1,9 @@
import contextlib
from . import QtWidgets, QtCore
from . import RecursiveSortFilterProxyModel, AssetModel, AssetView
from . import awesome, style
from . import RecursiveSortFilterProxyModel, AssetModel
from . import qtawesome, style
from . import TasksTemplateModel, DeselectableTreeView
from . import _iter_model_rows
@contextlib.contextmanager
def preserve_expanded_rows(tree_view,
@ -124,11 +124,11 @@ class AssetWidget(QtWidgets.QWidget):
selection_changed = QtCore.Signal() # on view selection change
current_changed = QtCore.Signal() # on view current index change
def __init__(self, parent):
def __init__(self, dbcon, parent=None):
super(AssetWidget, self).__init__(parent=parent)
self.setContentsMargins(0, 0, 0, 0)
self.parent_widget = parent
self.dbcon = dbcon
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
@ -139,17 +139,21 @@ class AssetWidget(QtWidgets.QWidget):
self._set_projects()
self.combo_projects.currentTextChanged.connect(self.on_project_change)
# Tree View
model = AssetModel(self)
model = AssetModel(dbcon=self.dbcon, parent=self)
proxy = RecursiveSortFilterProxyModel()
proxy.setSourceModel(model)
proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
view = AssetView()
view = DeselectableTreeView()
view.setIndentation(15)
view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
view.setHeaderHidden(True)
view.setModel(proxy)
# Header
header = QtWidgets.QHBoxLayout()
icon = awesome.icon("fa.refresh", color=style.colors.light)
icon = qtawesome.icon("fa.refresh", color=style.colors.light)
refresh = QtWidgets.QPushButton(icon, "")
refresh.setToolTip("Refresh items")
@ -195,13 +199,9 @@ class AssetWidget(QtWidgets.QWidget):
self.proxy = proxy
self.view = view
@property
def db(self):
return self.parent_widget.db
def collect_data(self):
project = self.db.find_one({'type': 'project'})
asset = self.db.find_one({'_id': self.get_active_asset()})
project = self.dbcon.find_one({'type': 'project'})
asset = self.dbcon.find_one({'_id': self.get_active_asset()})
try:
index = self.task_view.selectedIndexes()[0]
@ -211,41 +211,50 @@ class AssetWidget(QtWidgets.QWidget):
data = {
'project': project['name'],
'asset': asset['name'],
'silo': asset.get("silo")
'parents': self.get_parents(asset),
'task': task
}
return data
def get_parents(self, entity):
output = []
if entity.get('data', {}).get('visualParent', None) is None:
return output
parent = self.db.find_one({'_id': entity['data']['visualParent']})
parent = self.dbcon.find_one({'_id': entity['data']['visualParent']})
output.append(parent['name'])
output.extend(self.get_parents(parent))
return output
def _set_projects(self):
projects = list()
for project in self.db.projects():
for project in self.dbcon.projects():
projects.append(project['name'])
self.combo_projects.clear()
if len(projects) > 0:
self.combo_projects.addItems(projects)
self.db.activate_project(projects[0])
self.dbcon.activate_project(projects[0])
def on_project_change(self):
projects = list()
for project in self.db.projects():
for project in self.dbcon.projects():
projects.append(project['name'])
project_name = self.combo_projects.currentText()
if project_name in projects:
self.db.activate_project(project_name)
self.dbcon.activate_project(project_name)
self.refresh()
def _refresh_model(self):
self.model.refresh()
with preserve_expanded_rows(
self.view, column=0, role=self.model.ObjectIdRole
):
with preserve_selection(
self.view, column=0, role=self.model.ObjectIdRole
):
self.model.refresh()
self.assets_refreshed.emit()
def refresh(self):
@ -255,7 +264,7 @@ class AssetWidget(QtWidgets.QWidget):
tasks = []
selected = self.get_selected_assets()
if len(selected) == 1:
asset = self.db.find_one({
asset = self.dbcon.find_one({
"_id": selected[0], "type": "asset"
})
if asset:
@ -266,7 +275,7 @@ class AssetWidget(QtWidgets.QWidget):
def get_active_asset(self):
"""Return the asset id the current asset."""
current = self.view.currentIndex()
return current.data(self.model.ObjectIdRole)
return current.data(self.model.ItemRole)
def get_active_index(self):
return self.view.currentIndex()
@ -277,7 +286,7 @@ class AssetWidget(QtWidgets.QWidget):
rows = selection.selectedRows()
return [row.data(self.model.ObjectIdRole) for row in rows]
def select_assets(self, assets, expand=True):
def select_assets(self, assets, expand=True, key="name"):
"""Select assets by name.
Args:
@ -290,8 +299,14 @@ class AssetWidget(QtWidgets.QWidget):
"""
# TODO: Instead of individual selection optimize for many assets
assert isinstance(assets,
(tuple, list)), "Assets must be list or tuple"
if not isinstance(assets, (tuple, list)):
assets = [assets]
assert isinstance(
assets, (tuple, list)
), "Assets must be list or tuple"
# convert to list - tuple cant be modified
assets = list(assets)
# Clear selection
selection_model = self.view.selectionModel()
@ -299,16 +314,25 @@ class AssetWidget(QtWidgets.QWidget):
# Select
mode = selection_model.Select | selection_model.Rows
for index in _iter_model_rows(self.proxy,
column=0,
include_root=False):
data = index.data(self.model.NodeRole)
name = data['name']
if name in assets:
selection_model.select(index, mode)
for index in lib.iter_model_rows(
self.proxy, column=0, include_root=False
):
# stop iteration if there are no assets to process
if not assets:
break
if expand:
self.view.expand(index)
value = index.data(self.model.ItemRole).get(key)
if value not in assets:
continue
# Set the currently active index
self.view.setCurrentIndex(index)
# Remove processed asset
assets.pop(assets.index(value))
selection_model.select(index, mode)
if expand:
# Expand parent index
self.view.expand(self.proxy.parent(index))
# Set the currently active index
self.view.setCurrentIndex(index)

View file

@ -1,16 +0,0 @@
from . import QtCore
from . import DeselectableTreeView
class AssetView(DeselectableTreeView):
"""Item view.
This implements a context menu.
"""
def __init__(self):
super(AssetView, self).__init__()
self.setIndentation(15)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.setHeaderHidden(True)

View file

@ -19,11 +19,11 @@ class FamilyWidget(QtWidgets.QWidget):
Separator = "---separator---"
NOT_SELECTED = '< Nothing is selected >'
def __init__(self, parent):
super().__init__(parent)
def __init__(self, dbcon, parent=None):
super(FamilyWidget, self).__init__(parent=parent)
# Store internal states in here
self.state = {"valid": False}
self.parent_widget = parent
self.dbcon = dbcon
self.asset_name = self.NOT_SELECTED
body = QtWidgets.QWidget()
@ -67,7 +67,7 @@ class FamilyWidget(QtWidgets.QWidget):
layout = QtWidgets.QVBoxLayout(container)
header = FamilyDescriptionWidget(self)
header = FamilyDescriptionWidget(parent=self)
layout.addWidget(header)
layout.addWidget(QtWidgets.QLabel("Family"))
@ -124,10 +124,6 @@ class FamilyWidget(QtWidgets.QWidget):
}
return data
@property
def db(self):
return self.parent_widget.db
def change_asset(self, name):
if name is None:
name = self.NOT_SELECTED
@ -136,7 +132,6 @@ class FamilyWidget(QtWidgets.QWidget):
def _on_state_changed(self, state):
self.state['valid'] = state
self.parent_widget.set_valid_family(state)
def _build_menu(self, default_names):
"""Create optional predefined subset names
@ -183,7 +178,7 @@ class FamilyWidget(QtWidgets.QWidget):
assets = None
if asset_name != self.NOT_SELECTED:
# Get the assets from the database which match with the name
assets_db = self.db.find(
assets_db = self.dbcon.find(
filter={"type": "asset"},
projection={"name": 1}
)
@ -206,7 +201,7 @@ class FamilyWidget(QtWidgets.QWidget):
if assets:
# Get all subsets of the current asset
asset_ids = [asset["_id"] for asset in assets]
subsets = self.db.find(filter={"type": "subset",
subsets = self.dbcon.find(filter={"type": "subset",
"name": {"$regex": "{}*".format(family),
"$options": "i"},
"parent": {"$in": asset_ids}}) or []
@ -259,17 +254,17 @@ class FamilyWidget(QtWidgets.QWidget):
asset_name != self.NOT_SELECTED and
subset_name.strip() != ''
):
asset = self.db.find_one({
asset = self.dbcon.find_one({
'type': 'asset',
'name': asset_name
})
subset = self.db.find_one({
subset = self.dbcon.find_one({
'type': 'subset',
'parent': asset['_id'],
'name': subset_name
})
if subset:
versions = self.db.find({
versions = self.dbcon.find({
'type': 'version',
'parent': subset['_id']
})

View file

@ -5,7 +5,7 @@ import json
from . import QtWidgets, QtCore, QtGui
from . import HelpRole, FamilyRole, ExistsRole, PluginRole
from . import awesome
from . import qtawesome
from pype.vendor import six
from pype import lib as pypelib
@ -87,7 +87,7 @@ class FamilyDescriptionWidget(QtWidgets.QWidget):
plugin = item.data(PluginRole)
icon = getattr(plugin, "icon", "info-circle")
assert isinstance(icon, six.string_types)
icon = awesome.icon("fa.{}".format(icon), color="white")
icon = qtawesome.icon("fa.{}".format(icon), color="white")
pixmap = icon.pixmap(self.SIZE, self.SIZE)
pixmap = pixmap.scaled(self.SIZE, self.SIZE)