mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'develop' into feature/OP-2416_Launcher-tool-model
This commit is contained in:
commit
67d3b6cbe5
457 changed files with 20844 additions and 389957 deletions
|
|
@ -9,7 +9,11 @@ import appdirs
|
|||
from Qt import QtCore, QtGui
|
||||
from avalon.vendor import qtawesome
|
||||
from avalon import api
|
||||
from openpype.lib import ApplicationManager, JSONSettingRegistry
|
||||
from openpype.lib import JSONSettingRegistry
|
||||
from openpype.lib.applications import (
|
||||
CUSTOM_LAUNCH_APP_GROUPS,
|
||||
ApplicationManager
|
||||
)
|
||||
from openpype.tools.utils.lib import DynamicQThread
|
||||
from openpype.tools.utils.assets_widget import (
|
||||
AssetModel,
|
||||
|
|
@ -90,6 +94,9 @@ class ActionModel(QtGui.QStandardItemModel):
|
|||
if not app or not app.enabled:
|
||||
continue
|
||||
|
||||
if app.group.name in CUSTOM_LAUNCH_APP_GROUPS:
|
||||
continue
|
||||
|
||||
# Get from app definition, if not there from app in project
|
||||
action = type(
|
||||
"app_{}".format(app_name),
|
||||
|
|
@ -331,7 +338,7 @@ class ActionModel(QtGui.QStandardItemModel):
|
|||
action = action[0]
|
||||
|
||||
compare_data = {}
|
||||
if action:
|
||||
if action and action.label:
|
||||
compare_data = {
|
||||
"app_label": action.label.lower(),
|
||||
"project_name": self.dbcon.Session["AVALON_PROJECT"],
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from .models import (
|
|||
LauncherTaskModel,
|
||||
LauncherTasksProxyModel
|
||||
)
|
||||
from .actions import ApplicationAction
|
||||
from .constants import (
|
||||
ACTION_ROLE,
|
||||
GROUP_ROLE,
|
||||
|
|
@ -313,10 +314,12 @@ class ActionBar(QtWidgets.QWidget):
|
|||
is_variant_group = index.data(VARIANT_GROUP_ROLE)
|
||||
if not is_group and not is_variant_group:
|
||||
action = index.data(ACTION_ROLE)
|
||||
if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE):
|
||||
action.data["start_last_workfile"] = False
|
||||
else:
|
||||
action.data.pop("start_last_workfile", None)
|
||||
# Change data of application action
|
||||
if issubclass(action, ApplicationAction):
|
||||
if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE):
|
||||
action.data["start_last_workfile"] = False
|
||||
else:
|
||||
action.data.pop("start_last_workfile", None)
|
||||
self._start_animation(index)
|
||||
self.action_clicked.emit(action)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -396,9 +396,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
self._versionschanged()
|
||||
return
|
||||
|
||||
selected_subsets = self._subsets_widget.selected_subsets(
|
||||
_merged=True, _other=False
|
||||
)
|
||||
selected_subsets = self._subsets_widget.get_selected_merge_items()
|
||||
|
||||
asset_colors = {}
|
||||
asset_ids = []
|
||||
|
|
@ -423,35 +421,14 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
|
|||
self._versionschanged()
|
||||
|
||||
def _versionschanged(self):
|
||||
selection = self._subsets_widget.view.selectionModel()
|
||||
|
||||
# Active must be in the selected rows otherwise we
|
||||
# assume it's not actually an "active" current index.
|
||||
version_docs = None
|
||||
items = self._subsets_widget.get_selected_subsets()
|
||||
version_doc = None
|
||||
active = selection.currentIndex()
|
||||
rows = selection.selectedRows(column=active.column())
|
||||
if active and active in rows:
|
||||
item = active.data(self._subsets_widget.model.ItemRole)
|
||||
if (
|
||||
item is not None
|
||||
and not (item.get("isGroup") or item.get("isMerged"))
|
||||
):
|
||||
version_doc = item["version_document"]
|
||||
|
||||
if rows:
|
||||
version_docs = []
|
||||
for index in rows:
|
||||
if not index or not index.isValid():
|
||||
continue
|
||||
item = index.data(self._subsets_widget.model.ItemRole)
|
||||
if (
|
||||
item is None
|
||||
or item.get("isGroup")
|
||||
or item.get("isMerged")
|
||||
):
|
||||
continue
|
||||
version_docs.append(item["version_document"])
|
||||
version_docs = []
|
||||
for item in items:
|
||||
doc = item["version_document"]
|
||||
version_docs.append(doc)
|
||||
if version_doc is None:
|
||||
version_doc = doc
|
||||
|
||||
self._version_info_widget.set_version(version_doc)
|
||||
|
||||
|
|
|
|||
|
|
@ -287,9 +287,7 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
on selection change so they match current selection.
|
||||
"""
|
||||
# TODO do not touch inner attributes of asset widget
|
||||
last_asset_ids = self.data["state"]["assetIds"]
|
||||
if last_asset_ids:
|
||||
self._assets_widget.clear_underlines()
|
||||
self._assets_widget.clear_underlines()
|
||||
|
||||
def _assetschanged(self):
|
||||
"""Selected assets have changed"""
|
||||
|
|
@ -328,12 +326,11 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
asset_ids = self.data["state"]["assetIds"]
|
||||
# Skip setting colors if not asset multiselection
|
||||
if not asset_ids or len(asset_ids) < 2:
|
||||
self.clear_assets_underlines()
|
||||
self._versionschanged()
|
||||
return
|
||||
|
||||
selected_subsets = self._subsets_widget.selected_subsets(
|
||||
_merged=True, _other=False
|
||||
)
|
||||
selected_subsets = self._subsets_widget.get_selected_merge_items()
|
||||
|
||||
asset_colors = {}
|
||||
asset_ids = []
|
||||
|
|
@ -358,37 +355,16 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
self._versionschanged()
|
||||
|
||||
def _versionschanged(self):
|
||||
subsets = self._subsets_widget
|
||||
selection = subsets.view.selectionModel()
|
||||
|
||||
# Active must be in the selected rows otherwise we
|
||||
# assume it's not actually an "active" current index.
|
||||
items = self._subsets_widget.get_selected_subsets()
|
||||
version_doc = None
|
||||
active = selection.currentIndex()
|
||||
rows = selection.selectedRows(column=active.column())
|
||||
if active:
|
||||
if active in rows:
|
||||
item = active.data(subsets.model.ItemRole)
|
||||
if (
|
||||
item is not None and
|
||||
not (item.get("isGroup") or item.get("isMerged"))
|
||||
):
|
||||
version_doc = item["version_document"]
|
||||
self._version_info_widget.set_version(version_doc)
|
||||
|
||||
version_docs = []
|
||||
if rows:
|
||||
for index in rows:
|
||||
if not index or not index.isValid():
|
||||
continue
|
||||
item = index.data(subsets.model.ItemRole)
|
||||
if item is None:
|
||||
continue
|
||||
if item.get("isGroup") or item.get("isMerged"):
|
||||
for child in item.children():
|
||||
version_docs.append(child["version_document"])
|
||||
else:
|
||||
version_docs.append(item["version_document"])
|
||||
for item in items:
|
||||
doc = item["version_document"]
|
||||
version_docs.append(doc)
|
||||
if version_doc is None:
|
||||
version_doc = doc
|
||||
|
||||
self._version_info_widget.set_version(version_doc)
|
||||
|
||||
thumbnail_src_ids = [
|
||||
version_doc["_id"]
|
||||
|
|
@ -480,18 +456,7 @@ class LoaderWindow(QtWidgets.QDialog):
|
|||
self.echo("Grouping not enabled.")
|
||||
return
|
||||
|
||||
selected = []
|
||||
merged_items = []
|
||||
for item in subsets.selected_subsets(_merged=True):
|
||||
if item.get("isMerged"):
|
||||
merged_items.append(item)
|
||||
else:
|
||||
selected.append(item)
|
||||
|
||||
for merged_item in merged_items:
|
||||
for child_item in merged_item.children():
|
||||
selected.append(child_item)
|
||||
|
||||
selected = self._subsets_widget.get_selected_subsets()
|
||||
if not selected:
|
||||
self.echo("No selected subset.")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -18,26 +18,6 @@ def change_visibility(model, view, column_name, visible):
|
|||
view.setColumnHidden(index, not visible)
|
||||
|
||||
|
||||
def get_selected_items(rows, item_role):
|
||||
items = []
|
||||
for row_index in rows:
|
||||
item = row_index.data(item_role)
|
||||
if item.get("isGroup"):
|
||||
continue
|
||||
|
||||
elif item.get("isMerged"):
|
||||
for idx in range(row_index.model().rowCount(row_index)):
|
||||
child_index = row_index.child(idx, 0)
|
||||
item = child_index.data(item_role)
|
||||
if item not in items:
|
||||
items.append(item)
|
||||
|
||||
else:
|
||||
if item not in items:
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
|
||||
def get_options(action, loader, parent, repre_contexts):
|
||||
"""Provides dialog to select value from loader provided options.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import copy
|
||||
import re
|
||||
import math
|
||||
from uuid import uuid4
|
||||
|
||||
from avalon import (
|
||||
style,
|
||||
|
|
@ -22,6 +23,8 @@ from openpype.tools.utils.constants import (
|
|||
REMOTE_AVAILABILITY_ROLE
|
||||
)
|
||||
|
||||
ITEM_ID_ROLE = QtCore.Qt.UserRole + 90
|
||||
|
||||
|
||||
def is_filtering_recursible():
|
||||
"""Does Qt binding support recursive filtering for QSortFilterProxyModel?
|
||||
|
|
@ -179,6 +182,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
self._icons = {
|
||||
"subset": qtawesome.icon("fa.file-o", color=style.colors.default)
|
||||
}
|
||||
self._items_by_id = {}
|
||||
|
||||
self._doc_fetching_thread = None
|
||||
self._doc_fetching_stop = False
|
||||
|
|
@ -188,6 +192,15 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
|
||||
self.refresh()
|
||||
|
||||
def get_item_by_id(self, item_id):
|
||||
return self._items_by_id.get(item_id)
|
||||
|
||||
def add_child(self, new_item, *args, **kwargs):
|
||||
item_id = str(uuid4())
|
||||
new_item["id"] = item_id
|
||||
self._items_by_id[item_id] = new_item
|
||||
super(SubsetsModel, self).add_child(new_item, *args, **kwargs)
|
||||
|
||||
def set_assets(self, asset_ids):
|
||||
self._asset_ids = asset_ids
|
||||
self.refresh()
|
||||
|
|
@ -486,7 +499,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
def refresh(self):
|
||||
self.stop_fetch_thread()
|
||||
self.clear()
|
||||
|
||||
self._items_by_id = {}
|
||||
self.reset_sync_server()
|
||||
|
||||
if not self._asset_ids:
|
||||
|
|
@ -497,6 +510,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
|
||||
def on_doc_fetched(self):
|
||||
self.clear()
|
||||
self._items_by_id = {}
|
||||
self.beginResetModel()
|
||||
|
||||
asset_docs_by_id = self._doc_payload.get(
|
||||
|
|
@ -524,9 +538,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
return
|
||||
|
||||
self._fill_subset_items(
|
||||
asset_docs_by_id, subset_docs_by_id, last_versions_by_subset_id,
|
||||
asset_docs_by_id,
|
||||
subset_docs_by_id,
|
||||
last_versions_by_subset_id,
|
||||
repre_info_by_version_id
|
||||
)
|
||||
self.endResetModel()
|
||||
self.refreshed.emit(True)
|
||||
|
||||
def create_multiasset_group(
|
||||
self, subset_name, asset_ids, subset_counter, parent_item=None
|
||||
|
|
@ -538,7 +556,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
merge_group.update({
|
||||
"subset": "{} ({})".format(subset_name, len(asset_ids)),
|
||||
"isMerged": True,
|
||||
"childRow": 0,
|
||||
"subsetColor": subset_color,
|
||||
"assetIds": list(asset_ids),
|
||||
"icon": qtawesome.icon(
|
||||
|
|
@ -547,7 +564,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
)
|
||||
})
|
||||
|
||||
subset_counter += 1
|
||||
self.add_child(merge_group, parent_item)
|
||||
|
||||
return merge_group
|
||||
|
|
@ -567,8 +583,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
group_item = Item()
|
||||
group_item.update({
|
||||
"subset": group_name,
|
||||
"isGroup": True,
|
||||
"childRow": 0
|
||||
"isGroup": True
|
||||
})
|
||||
group_item.update(group_data)
|
||||
|
||||
|
|
@ -666,14 +681,14 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
|
|||
index = self.index(item.row(), 0, parent_index)
|
||||
self.set_version(index, last_version)
|
||||
|
||||
self.endResetModel()
|
||||
self.refreshed.emit(True)
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return
|
||||
|
||||
item = index.internalPointer()
|
||||
if role == ITEM_ID_ROLE:
|
||||
return item["id"]
|
||||
|
||||
if role == self.SortDescendingRole:
|
||||
if item.get("isGroup"):
|
||||
# Ensure groups be on top when sorting by descending order
|
||||
|
|
@ -1053,6 +1068,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
|
|||
self._icons = lib.get_repre_icons()
|
||||
self._icons["repre"] = qtawesome.icon("fa.file-o",
|
||||
color=style.colors.default)
|
||||
self._items_by_id = {}
|
||||
|
||||
def set_version_ids(self, version_ids):
|
||||
self.version_ids = version_ids
|
||||
|
|
@ -1061,6 +1077,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
|
|||
def data(self, index, role):
|
||||
item = index.internalPointer()
|
||||
|
||||
if role == ITEM_ID_ROLE:
|
||||
return item["id"]
|
||||
|
||||
if role == self.IdRole:
|
||||
return item.get("_id")
|
||||
|
||||
|
|
@ -1134,12 +1153,14 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
|
|||
if len(self.version_ids) > 1:
|
||||
group = repre_groups.get(doc["name"])
|
||||
if not group:
|
||||
|
||||
group_item = Item()
|
||||
item_id = str(uuid4())
|
||||
group_item.update({
|
||||
"id": item_id,
|
||||
"_id": doc["_id"],
|
||||
"name": doc["name"],
|
||||
"isMerged": True,
|
||||
"childRow": 0,
|
||||
"active_site_name": self.active_site,
|
||||
"remote_site_name": self.remote_site,
|
||||
"icon": qtawesome.icon(
|
||||
|
|
@ -1147,6 +1168,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
|
|||
color=style.colors.default
|
||||
)
|
||||
})
|
||||
self._items_by_id[item_id] = group_item
|
||||
self.add_child(group_item, None)
|
||||
repre_groups[doc["name"]] = group_item
|
||||
repre_groups_items[doc["name"]] = 0
|
||||
|
|
@ -1159,7 +1181,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
|
|||
active_site_icon = self._icons.get(self.active_provider)
|
||||
remote_site_icon = self._icons.get(self.remote_provider)
|
||||
|
||||
item_id = str(uuid4())
|
||||
data = {
|
||||
"id": item_id,
|
||||
"_id": doc["_id"],
|
||||
"name": doc["name"],
|
||||
"subset": doc["context"]["subset"],
|
||||
|
|
@ -1178,6 +1202,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
|
|||
|
||||
item = Item()
|
||||
item.update(data)
|
||||
self._items_by_id[item_id] = item
|
||||
|
||||
current_progress = {
|
||||
'active_site_progress': progress[self.active_site],
|
||||
|
|
@ -1201,6 +1226,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
|
|||
self.endResetModel()
|
||||
self.refreshed.emit(False)
|
||||
|
||||
def get_item_by_id(self, item_id):
|
||||
return self._items_by_id.get(item_id)
|
||||
|
||||
def refresh(self):
|
||||
docs = []
|
||||
session_project = self.dbcon.Session['AVALON_PROJECT']
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ from .model import (
|
|||
SubsetFilterProxyModel,
|
||||
FamiliesFilterProxyModel,
|
||||
RepresentationModel,
|
||||
RepresentationSortProxyModel
|
||||
RepresentationSortProxyModel,
|
||||
ITEM_ID_ROLE
|
||||
)
|
||||
from . import lib
|
||||
|
||||
|
|
@ -351,6 +352,59 @@ class SubsetWidget(QtWidgets.QWidget):
|
|||
|
||||
lib.change_visibility(self.model, self.view, "repre_info", enabled)
|
||||
|
||||
def get_selected_items(self):
|
||||
selection_model = self.view.selectionModel()
|
||||
indexes = selection_model.selectedIndexes()
|
||||
|
||||
item_ids = set()
|
||||
for index in indexes:
|
||||
item_id = index.data(ITEM_ID_ROLE)
|
||||
if item_id is not None:
|
||||
item_ids.add(item_id)
|
||||
|
||||
output = []
|
||||
for item_id in item_ids:
|
||||
item = self.model.get_item_by_id(item_id)
|
||||
if item is not None:
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
def get_selected_merge_items(self):
|
||||
output = []
|
||||
items = collections.deque(self.get_selected_items())
|
||||
|
||||
item_ids = set()
|
||||
while items:
|
||||
item = items.popleft()
|
||||
if item.get("isGroup"):
|
||||
for child in item.children():
|
||||
items.appendleft(child)
|
||||
|
||||
elif item.get("isMerged"):
|
||||
item_id = item["id"]
|
||||
if item_id not in item_ids:
|
||||
item_ids.add(item_id)
|
||||
output.append(item)
|
||||
|
||||
return output
|
||||
|
||||
def get_selected_subsets(self):
|
||||
output = []
|
||||
items = collections.deque(self.get_selected_items())
|
||||
|
||||
item_ids = set()
|
||||
while items:
|
||||
item = items.popleft()
|
||||
if item.get("isGroup") or item.get("isMerged"):
|
||||
for child in item.children():
|
||||
items.appendleft(child)
|
||||
else:
|
||||
item_id = item["id"]
|
||||
if item_id not in item_ids:
|
||||
item_ids.add(item_id)
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
def on_context_menu(self, point):
|
||||
"""Shows menu with loader actions on Right-click.
|
||||
|
||||
|
|
@ -367,10 +421,7 @@ class SubsetWidget(QtWidgets.QWidget):
|
|||
return
|
||||
|
||||
# Get selected subsets without groups
|
||||
selection = self.view.selectionModel()
|
||||
rows = selection.selectedRows(column=0)
|
||||
|
||||
items = lib.get_selected_items(rows, self.model.ItemRole)
|
||||
items = self.get_selected_subsets()
|
||||
|
||||
# Get all representation->loader combinations available for the
|
||||
# index under the cursor, so we can list the user the options.
|
||||
|
|
@ -539,35 +590,6 @@ class SubsetWidget(QtWidgets.QWidget):
|
|||
box = LoadErrorMessageBox(error_info, self)
|
||||
box.show()
|
||||
|
||||
def selected_subsets(self, _groups=False, _merged=False, _other=True):
|
||||
selection = self.view.selectionModel()
|
||||
rows = selection.selectedRows(column=0)
|
||||
|
||||
subsets = list()
|
||||
if not any([_groups, _merged, _other]):
|
||||
self.echo((
|
||||
"This is a BUG: Selected_subsets args must contain"
|
||||
" at least one value set to True"
|
||||
))
|
||||
return subsets
|
||||
|
||||
for row in rows:
|
||||
item = row.data(self.model.ItemRole)
|
||||
if item.get("isGroup"):
|
||||
if not _groups:
|
||||
continue
|
||||
|
||||
elif item.get("isMerged"):
|
||||
if not _merged:
|
||||
continue
|
||||
else:
|
||||
if not _other:
|
||||
continue
|
||||
|
||||
subsets.append(item)
|
||||
|
||||
return subsets
|
||||
|
||||
def group_subsets(self, name, asset_ids, items):
|
||||
field = "data.subsetGroup"
|
||||
|
||||
|
|
@ -1261,6 +1283,40 @@ class RepresentationWidget(QtWidgets.QWidget):
|
|||
}
|
||||
return repre_context_by_id
|
||||
|
||||
def get_selected_items(self):
|
||||
selection_model = self.tree_view.selectionModel()
|
||||
indexes = selection_model.selectedIndexes()
|
||||
|
||||
item_ids = set()
|
||||
for index in indexes:
|
||||
item_id = index.data(ITEM_ID_ROLE)
|
||||
if item_id is not None:
|
||||
item_ids.add(item_id)
|
||||
|
||||
output = []
|
||||
for item_id in item_ids:
|
||||
item = self.model.get_item_by_id(item_id)
|
||||
if item is not None:
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
def get_selected_repre_items(self):
|
||||
output = []
|
||||
items = collections.deque(self.get_selected_items())
|
||||
|
||||
item_ids = set()
|
||||
while items:
|
||||
item = items.popleft()
|
||||
if item.get("isGroup") or item.get("isMerged"):
|
||||
for child in item.children():
|
||||
items.appendleft(child)
|
||||
else:
|
||||
item_id = item["id"]
|
||||
if item_id not in item_ids:
|
||||
item_ids.add(item_id)
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
def on_context_menu(self, point):
|
||||
"""Shows menu with loader actions on Right-click.
|
||||
|
||||
|
|
@ -1279,10 +1335,8 @@ class RepresentationWidget(QtWidgets.QWidget):
|
|||
selection = self.tree_view.selectionModel()
|
||||
rows = selection.selectedRows(column=0)
|
||||
|
||||
items = lib.get_selected_items(rows, self.model.ItemRole)
|
||||
|
||||
items = self.get_selected_repre_items()
|
||||
selected_side = self._get_selected_side(point_index, rows)
|
||||
|
||||
# Get all representation->loader combinations available for the
|
||||
# index under the cursor, so we can list the user the options.
|
||||
available_loaders = api.discover(api.Loader)
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ def get_all_asset_nodes():
|
|||
|
||||
# Gather all information
|
||||
container_name = container["objectName"]
|
||||
nodes += cmds.sets(container_name, query=True, nodesOnly=True) or []
|
||||
nodes += lib.get_container_members(container_name)
|
||||
|
||||
nodes = list(set(nodes))
|
||||
return nodes
|
||||
|
|
@ -195,7 +195,7 @@ def remove_unused_looks():
|
|||
unused = []
|
||||
for container in host.ls():
|
||||
if container['loader'] == "LookLoader":
|
||||
members = cmds.sets(container['objectName'], query=True)
|
||||
members = lib.get_container_members(container['objectName'])
|
||||
look_sets = cmds.ls(members, type="objectSet")
|
||||
for look_set in look_sets:
|
||||
# If the set is used than we consider this look *in use*
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ from collections import defaultdict
|
|||
|
||||
from Qt import QtCore
|
||||
|
||||
from avalon.tools import models
|
||||
from avalon.vendor import qtawesome
|
||||
from avalon.style import colors
|
||||
from openpype.tools.utils import models
|
||||
|
||||
|
||||
class AssetModel(models.TreeModel):
|
||||
|
|
|
|||
|
|
@ -10,11 +10,9 @@ import six
|
|||
import alembic.Abc
|
||||
from maya import cmds
|
||||
|
||||
import avalon.io as io
|
||||
import avalon.maya
|
||||
import avalon.api as api
|
||||
from avalon import io, api
|
||||
|
||||
import openpype.hosts.maya.api.lib as lib
|
||||
from openpype.hosts.maya.api import lib
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -203,10 +201,12 @@ def load_look(version_id):
|
|||
raise RuntimeError("Could not find LookLoader, this is a bug")
|
||||
|
||||
# Reference the look file
|
||||
with avalon.maya.maintained_selection():
|
||||
with lib.maintained_selection():
|
||||
container_node = api.load(loader, look_representation)
|
||||
|
||||
return cmds.sets(container_node, query=True)
|
||||
# Get container members
|
||||
shader_nodes = lib.get_container_members(container_node)
|
||||
return shader_nodes
|
||||
|
||||
|
||||
def get_latest_version(asset_id, subset):
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ from . import views
|
|||
|
||||
from maya import cmds
|
||||
|
||||
MODELINDEX = QtCore.QModelIndex()
|
||||
|
||||
|
||||
class AssetOutliner(QtWidgets.QWidget):
|
||||
refreshed = QtCore.Signal()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ from Qt import QtWidgets, QtCore
|
|||
|
||||
from .widgets import (
|
||||
NameTextEdit,
|
||||
FilterComboBox
|
||||
FilterComboBox,
|
||||
SpinBoxScrollFixed,
|
||||
DoubleSpinBoxScrollFixed
|
||||
)
|
||||
from .multiselection_combobox import MultiSelectionComboBox
|
||||
|
||||
|
|
@ -89,9 +91,9 @@ class NumberDelegate(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
def createEditor(self, parent, option, index):
|
||||
if self.decimals > 0:
|
||||
editor = QtWidgets.QDoubleSpinBox(parent)
|
||||
editor = DoubleSpinBoxScrollFixed(parent)
|
||||
else:
|
||||
editor = QtWidgets.QSpinBox(parent)
|
||||
editor = SpinBoxScrollFixed(parent)
|
||||
|
||||
editor.setObjectName("NumberEditor")
|
||||
# Set min/max
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import os
|
||||
from Qt import QtCore, QtGui
|
||||
|
||||
from openpype.style import get_objected_colors
|
||||
from avalon.vendor import qtawesome
|
||||
from openpype.tools.utils import paint_image_with_color
|
||||
|
||||
|
||||
class ResourceCache:
|
||||
|
|
@ -91,17 +91,6 @@ class ResourceCache:
|
|||
icon.addPixmap(disabled_pix, QtGui.QIcon.Disabled, QtGui.QIcon.Off)
|
||||
return icon
|
||||
|
||||
@classmethod
|
||||
def get_warning_pixmap(cls):
|
||||
src_image = get_warning_image()
|
||||
colors = get_objected_colors()
|
||||
color_value = colors["delete-btn-bg"]
|
||||
|
||||
return paint_image_with_color(
|
||||
src_image,
|
||||
color_value.get_qcolor()
|
||||
)
|
||||
|
||||
|
||||
def get_remove_image():
|
||||
image_path = os.path.join(
|
||||
|
|
@ -110,36 +99,3 @@ def get_remove_image():
|
|||
"bin.png"
|
||||
)
|
||||
return QtGui.QImage(image_path)
|
||||
|
||||
|
||||
def get_warning_image():
|
||||
image_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"images",
|
||||
"warning.png"
|
||||
)
|
||||
return QtGui.QImage(image_path)
|
||||
|
||||
|
||||
def paint_image_with_color(image, color):
|
||||
"""TODO: This function should be imported from utils.
|
||||
|
||||
At the moment of creation is not available yet.
|
||||
"""
|
||||
width = image.width()
|
||||
height = image.height()
|
||||
|
||||
alpha_mask = image.createAlphaMask()
|
||||
alpha_region = QtGui.QRegion(QtGui.QBitmap.fromImage(alpha_mask))
|
||||
|
||||
pixmap = QtGui.QPixmap(width, height)
|
||||
pixmap.fill(QtCore.Qt.transparent)
|
||||
|
||||
painter = QtGui.QPainter(pixmap)
|
||||
painter.setClipRegion(alpha_region)
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.setBrush(color)
|
||||
painter.drawRect(QtCore.QRect(0, 0, width, height))
|
||||
painter.end()
|
||||
|
||||
return pixmap
|
||||
|
|
|
|||
|
|
@ -4,14 +4,16 @@ from .constants import (
|
|||
NAME_ALLOWED_SYMBOLS,
|
||||
NAME_REGEX
|
||||
)
|
||||
from .style import ResourceCache
|
||||
from openpype.lib import (
|
||||
create_project,
|
||||
PROJECT_NAME_ALLOWED_SYMBOLS,
|
||||
PROJECT_NAME_REGEX
|
||||
)
|
||||
from openpype.style import load_stylesheet
|
||||
from openpype.tools.utils import PlaceholderLineEdit
|
||||
from openpype.tools.utils import (
|
||||
PlaceholderLineEdit,
|
||||
get_warning_pixmap
|
||||
)
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
|
@ -338,7 +340,7 @@ class ConfirmProjectDeletion(QtWidgets.QDialog):
|
|||
|
||||
top_widget = QtWidgets.QWidget(self)
|
||||
|
||||
warning_pixmap = ResourceCache.get_warning_pixmap()
|
||||
warning_pixmap = get_warning_pixmap()
|
||||
warning_icon_label = PixmapLabel(warning_pixmap, top_widget)
|
||||
|
||||
message_label = QtWidgets.QLabel(top_widget)
|
||||
|
|
@ -429,3 +431,29 @@ class ConfirmProjectDeletion(QtWidgets.QDialog):
|
|||
def _on_confirm_text_change(self):
|
||||
enabled = self._confirm_input.text() == self._project_name
|
||||
self._confirm_btn.setEnabled(enabled)
|
||||
|
||||
|
||||
class SpinBoxScrollFixed(QtWidgets.QSpinBox):
|
||||
"""QSpinBox which only allow edits change with scroll wheel when active"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SpinBoxScrollFixed, self).__init__(*args, **kwargs)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if not self.hasFocus():
|
||||
event.ignore()
|
||||
else:
|
||||
super(SpinBoxScrollFixed, self).wheelEvent(event)
|
||||
|
||||
|
||||
class DoubleSpinBoxScrollFixed(QtWidgets.QDoubleSpinBox):
|
||||
"""QDoubleSpinBox which only allow edits with scroll wheel when active"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DoubleSpinBoxScrollFixed, self).__init__(*args, **kwargs)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if not self.hasFocus():
|
||||
event.ignore()
|
||||
else:
|
||||
super(DoubleSpinBoxScrollFixed, self).wheelEvent(event)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,9 @@ class ProjectManagerWindow(QtWidgets.QWidget):
|
|||
helper_btns_widget
|
||||
)
|
||||
add_asset_btn.setObjectName("IconBtn")
|
||||
add_asset_btn.setEnabled(False)
|
||||
add_task_btn.setObjectName("IconBtn")
|
||||
add_task_btn.setEnabled(False)
|
||||
|
||||
helper_btns_layout = QtWidgets.QHBoxLayout(helper_btns_widget)
|
||||
helper_btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -138,6 +140,7 @@ class ProjectManagerWindow(QtWidgets.QWidget):
|
|||
|
||||
message_label = QtWidgets.QLabel(buttons_widget)
|
||||
save_btn = QtWidgets.QPushButton("Save", buttons_widget)
|
||||
save_btn.setEnabled(False)
|
||||
|
||||
buttons_layout = QtWidgets.QHBoxLayout(buttons_widget)
|
||||
buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -173,6 +176,7 @@ class ProjectManagerWindow(QtWidgets.QWidget):
|
|||
self._create_project_btn = create_project_btn
|
||||
self._create_folders_btn = create_folders_btn
|
||||
self._remove_projects_btn = remove_projects_btn
|
||||
self._save_btn = save_btn
|
||||
|
||||
self._add_asset_btn = add_asset_btn
|
||||
self._add_task_btn = add_task_btn
|
||||
|
|
@ -183,6 +187,9 @@ class ProjectManagerWindow(QtWidgets.QWidget):
|
|||
def _set_project(self, project_name=None):
|
||||
self._create_folders_btn.setEnabled(project_name is not None)
|
||||
self._remove_projects_btn.setEnabled(project_name is not None)
|
||||
self._add_asset_btn.setEnabled(project_name is not None)
|
||||
self._add_task_btn.setEnabled(project_name is not None)
|
||||
self._save_btn.setEnabled(project_name is not None)
|
||||
self._project_proxy_model.set_filter_default(project_name is not None)
|
||||
self.hierarchy_view.set_project(project_name)
|
||||
|
||||
|
|
|
|||
|
|
@ -62,9 +62,9 @@ def install_fonts():
|
|||
# In hosts, this will be called each time the GUI is shown,
|
||||
# potentially installing a font each time.
|
||||
if database.addApplicationFont(path) < 0:
|
||||
print("Could not install %s\n" % path)
|
||||
print("Could not install %s" % path)
|
||||
else:
|
||||
print("Installed %s\n" % font)
|
||||
print("Installed %s" % font)
|
||||
|
||||
|
||||
def on_destroyed():
|
||||
|
|
|
|||
|
|
@ -47,18 +47,22 @@ class MainThreadProcess(QtCore.QObject):
|
|||
|
||||
This approach gives ability to update UI meanwhile plugin is in progress.
|
||||
"""
|
||||
timer_interval = 3
|
||||
# How many times let pass QtApplication to process events
|
||||
# - use 2 as resize event can trigger repaint event but not process in
|
||||
# same loop
|
||||
count_timeout = 2
|
||||
|
||||
def __init__(self):
|
||||
super(MainThreadProcess, self).__init__()
|
||||
self._items_to_process = collections.deque()
|
||||
|
||||
timer = QtCore.QTimer()
|
||||
timer.setInterval(self.timer_interval)
|
||||
timer.setInterval(0)
|
||||
|
||||
timer.timeout.connect(self._execute)
|
||||
|
||||
self._timer = timer
|
||||
self._switch_counter = self.count_timeout
|
||||
|
||||
def process(self, func, *args, **kwargs):
|
||||
item = MainThreadItem(func, *args, **kwargs)
|
||||
|
|
@ -71,6 +75,12 @@ class MainThreadProcess(QtCore.QObject):
|
|||
if not self._items_to_process:
|
||||
return
|
||||
|
||||
if self._switch_counter > 0:
|
||||
self._switch_counter -= 1
|
||||
return
|
||||
|
||||
self._switch_counter = self.count_timeout
|
||||
|
||||
item = self._items_to_process.popleft()
|
||||
item.process()
|
||||
|
||||
|
|
@ -142,6 +152,8 @@ class Controller(QtCore.QObject):
|
|||
self.instance_toggled.connect(self._on_instance_toggled)
|
||||
self._main_thread_processor = MainThreadProcess()
|
||||
|
||||
self._current_state = ""
|
||||
|
||||
def reset_variables(self):
|
||||
self.log.debug("Resetting pyblish context variables")
|
||||
|
||||
|
|
@ -149,6 +161,7 @@ class Controller(QtCore.QObject):
|
|||
self.is_running = False
|
||||
self.stopped = False
|
||||
self.errored = False
|
||||
self._current_state = ""
|
||||
|
||||
# Active producer of pairs
|
||||
self.pair_generator = None
|
||||
|
|
@ -157,7 +170,6 @@ class Controller(QtCore.QObject):
|
|||
|
||||
# Orders which changes GUI
|
||||
# - passing collectors order disables plugin/instance toggle
|
||||
self.collectors_order = None
|
||||
self.collect_state = 0
|
||||
|
||||
# - passing validators order disables validate button and gives ability
|
||||
|
|
@ -166,11 +178,8 @@ class Controller(QtCore.QObject):
|
|||
self.validated = False
|
||||
|
||||
# Get collectors and validators order
|
||||
self.order_groups.reset()
|
||||
plugin_groups = self.order_groups.groups()
|
||||
plugin_groups_keys = list(plugin_groups.keys())
|
||||
self.collectors_order = plugin_groups_keys[0]
|
||||
self.validators_order = self.order_groups.validation_order()
|
||||
plugin_groups_keys = list(self.order_groups.groups.keys())
|
||||
self.validators_order = self.order_groups.validation_order
|
||||
next_group_order = None
|
||||
if len(plugin_groups_keys) > 1:
|
||||
next_group_order = plugin_groups_keys[1]
|
||||
|
|
@ -181,13 +190,18 @@ class Controller(QtCore.QObject):
|
|||
"stop_on_validation": False,
|
||||
# Used?
|
||||
"last_plugin_order": None,
|
||||
"current_group_order": self.collectors_order,
|
||||
"current_group_order": plugin_groups_keys[0],
|
||||
"next_group_order": next_group_order,
|
||||
"nextOrder": None,
|
||||
"ordersWithError": set()
|
||||
}
|
||||
self._set_state_by_order()
|
||||
self.log.debug("Reset of pyblish context variables done")
|
||||
|
||||
@property
|
||||
def current_state(self):
|
||||
return self._current_state
|
||||
|
||||
def presets_by_hosts(self):
|
||||
# Get global filters as base
|
||||
presets = get_project_settings(os.environ['AVALON_PROJECT']) or {}
|
||||
|
|
@ -283,6 +297,9 @@ class Controller(QtCore.QObject):
|
|||
def on_published(self):
|
||||
if self.is_running:
|
||||
self.is_running = False
|
||||
self._current_state = (
|
||||
"Published" if not self.errored else "Published, with errors"
|
||||
)
|
||||
self.was_finished.emit()
|
||||
self._main_thread_processor.stop()
|
||||
|
||||
|
|
@ -345,7 +362,7 @@ class Controller(QtCore.QObject):
|
|||
new_current_group_order = self.processing["next_group_order"]
|
||||
if new_current_group_order is not None:
|
||||
current_next_order_found = False
|
||||
for order in self.order_groups.groups().keys():
|
||||
for order in self.order_groups.groups.keys():
|
||||
if current_next_order_found:
|
||||
new_next_group_order = order
|
||||
break
|
||||
|
|
@ -360,6 +377,10 @@ class Controller(QtCore.QObject):
|
|||
|
||||
if self.collect_state == 0:
|
||||
self.collect_state = 1
|
||||
self._current_state = (
|
||||
"Ready" if not self.errored else
|
||||
"Collected, with errors"
|
||||
)
|
||||
self.switch_toggleability.emit(True)
|
||||
self.passed_group.emit(current_group_order)
|
||||
yield IterationBreak("Collected")
|
||||
|
|
@ -367,6 +388,11 @@ class Controller(QtCore.QObject):
|
|||
else:
|
||||
self.passed_group.emit(current_group_order)
|
||||
if self.errored:
|
||||
self._current_state = (
|
||||
"Stopped, due to errors" if not
|
||||
self.processing["stop_on_validation"] else
|
||||
"Validated, with errors"
|
||||
)
|
||||
yield IterationBreak("Last group errored")
|
||||
|
||||
if self.collect_state == 1:
|
||||
|
|
@ -376,17 +402,23 @@ class Controller(QtCore.QObject):
|
|||
if not self.validated and plugin.order > self.validators_order:
|
||||
self.validated = True
|
||||
if self.processing["stop_on_validation"]:
|
||||
self._current_state = (
|
||||
"Validated" if not self.errored else
|
||||
"Validated, with errors"
|
||||
)
|
||||
yield IterationBreak("Validated")
|
||||
|
||||
# Stop if was stopped
|
||||
if self.stopped:
|
||||
self.stopped = False
|
||||
self._current_state = "Paused"
|
||||
yield IterationBreak("Stopped")
|
||||
|
||||
# check test if will stop
|
||||
self.processing["nextOrder"] = plugin.order
|
||||
message = self.test(**self.processing)
|
||||
if message:
|
||||
self._current_state = "Paused"
|
||||
yield IterationBreak("Stopped due to \"{}\"".format(message))
|
||||
|
||||
self.processing["last_plugin_order"] = plugin.order
|
||||
|
|
@ -416,6 +448,7 @@ class Controller(QtCore.QObject):
|
|||
# Stop if was stopped
|
||||
if self.stopped:
|
||||
self.stopped = False
|
||||
self._current_state = "Paused"
|
||||
yield IterationBreak("Stopped")
|
||||
|
||||
yield (plugin, instance)
|
||||
|
|
@ -526,20 +559,27 @@ class Controller(QtCore.QObject):
|
|||
MainThreadItem(on_next)
|
||||
)
|
||||
|
||||
def _set_state_by_order(self):
|
||||
order = self.processing["current_group_order"]
|
||||
self._current_state = self.order_groups.groups[order]["state"]
|
||||
|
||||
def collect(self):
|
||||
""" Iterate and process Collect plugins
|
||||
- load_plugins method is launched again when finished
|
||||
"""
|
||||
self._set_state_by_order()
|
||||
self._main_thread_processor.process(self._start_collect)
|
||||
self._main_thread_processor.start()
|
||||
|
||||
def validate(self):
|
||||
""" Process plugins to validations_order value."""
|
||||
self._set_state_by_order()
|
||||
self._main_thread_processor.process(self._start_validate)
|
||||
self._main_thread_processor.start()
|
||||
|
||||
def publish(self):
|
||||
""" Iterate and process all remaining plugins."""
|
||||
self._set_state_by_order()
|
||||
self._main_thread_processor.process(self._start_publish)
|
||||
self._main_thread_processor.start()
|
||||
|
||||
|
|
|
|||
|
|
@ -428,12 +428,12 @@ class PluginModel(QtGui.QStandardItemModel):
|
|||
self.clear()
|
||||
|
||||
def append(self, plugin):
|
||||
plugin_groups = self.controller.order_groups.groups()
|
||||
plugin_groups = self.controller.order_groups.groups
|
||||
label = None
|
||||
order = None
|
||||
for _order, _label in reversed(plugin_groups.items()):
|
||||
for _order, item in reversed(plugin_groups.items()):
|
||||
if _order is None or plugin.order < _order:
|
||||
label = _label
|
||||
label = item["label"]
|
||||
order = _order
|
||||
else:
|
||||
break
|
||||
|
|
|
|||
|
|
@ -25,3 +25,6 @@ TerminalFilters = {
|
|||
|
||||
# Allow animations in GUI
|
||||
Animated = env_variable_to_bool("OPENPYPE_PYBLISH_ANIMATED", True)
|
||||
|
||||
# Print UI info message to console
|
||||
PrintInfo = env_variable_to_bool("OPENPYPE_PYBLISH_PRINT_INFO", True)
|
||||
|
|
|
|||
|
|
@ -95,224 +95,44 @@ def collect_families_from_instances(instances, only_active=False):
|
|||
|
||||
|
||||
class OrderGroups:
|
||||
# Validator order can be set with environment "PYBLISH_VALIDATION_ORDER"
|
||||
# - this variable sets when validation button will hide and proecssing
|
||||
# of validation will end with ability to continue in process
|
||||
default_validation_order = pyblish.api.ValidatorOrder + 0.5
|
||||
|
||||
# Group range can be set with environment "PYBLISH_GROUP_RANGE"
|
||||
default_group_range = 1
|
||||
|
||||
# Group string can be set with environment "PYBLISH_GROUP_SETTING"
|
||||
default_groups = {
|
||||
pyblish.api.CollectorOrder + 0.5: "Collect",
|
||||
pyblish.api.ValidatorOrder + 0.5: "Validate",
|
||||
pyblish.api.ExtractorOrder + 0.5: "Extract",
|
||||
pyblish.api.IntegratorOrder + 0.5: "Integrate",
|
||||
None: "Other"
|
||||
}
|
||||
|
||||
# *** This example should have same result as is `default_groups` if
|
||||
# `group_range` is set to "1"
|
||||
__groups_str_example__ = (
|
||||
# half of `group_range` is added to 0 because number means it is Order
|
||||
"0=Collect"
|
||||
# if `<` is before than it means group range is not used
|
||||
# but is expected that number is already max
|
||||
",<1.5=Validate"
|
||||
# "Extractor" will be used in range `<1.5; 2.5)`
|
||||
",<2.5=Extract"
|
||||
",<3.5=Integrate"
|
||||
# "Other" if number is not set than all remaining plugins are in
|
||||
# - in this case Other's range is <3.5; infinity)
|
||||
",Other"
|
||||
)
|
||||
|
||||
_groups = None
|
||||
_validation_order = None
|
||||
_group_range = None
|
||||
|
||||
def __init__(
|
||||
self, group_str=None, group_range=None, validation_order=None
|
||||
):
|
||||
super(OrderGroups, self).__init__()
|
||||
# Override class methods with object methods
|
||||
self.groups = self._object_groups
|
||||
self.validation_order = self._object_validation_order
|
||||
self.group_range = self._object_group_range
|
||||
self.reset = self._object_reset
|
||||
|
||||
# set
|
||||
if group_range is not None:
|
||||
self._group_range = self.parse_group_range(
|
||||
group_range
|
||||
)
|
||||
|
||||
if group_str is not None:
|
||||
self._groups = self.parse_group_str(
|
||||
group_str
|
||||
)
|
||||
|
||||
if validation_order is not None:
|
||||
self._validation_order = self.parse_validation_order(
|
||||
validation_order
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _groups_method(obj):
|
||||
if obj._groups is None:
|
||||
obj._groups = obj.parse_group_str(
|
||||
group_range=obj.group_range()
|
||||
)
|
||||
return obj._groups
|
||||
|
||||
@staticmethod
|
||||
def _reset_method(obj):
|
||||
obj._groups = None
|
||||
obj._validation_order = None
|
||||
obj._group_range = None
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
return cls._reset_method(cls)
|
||||
|
||||
def _object_reset(self):
|
||||
return self._reset_method(self)
|
||||
|
||||
@classmethod
|
||||
def groups(cls):
|
||||
return cls._groups_method(cls)
|
||||
|
||||
def _object_groups(self):
|
||||
return self._groups_method(self)
|
||||
|
||||
@staticmethod
|
||||
def _validation_order_method(obj):
|
||||
if obj._validation_order is None:
|
||||
obj._validation_order = obj.parse_validation_order(
|
||||
group_range=obj.group_range()
|
||||
)
|
||||
return obj._validation_order
|
||||
|
||||
@classmethod
|
||||
def validation_order(cls):
|
||||
return cls._validation_order_method(cls)
|
||||
|
||||
def _object_validation_order(self):
|
||||
return self._validation_order_method(self)
|
||||
|
||||
@staticmethod
|
||||
def _group_range_method(obj):
|
||||
if obj._group_range is None:
|
||||
obj._group_range = obj.parse_group_range()
|
||||
return obj._group_range
|
||||
|
||||
@classmethod
|
||||
def group_range(cls):
|
||||
return cls._group_range_method(cls)
|
||||
|
||||
def _object_group_range(self):
|
||||
return self._group_range_method(self)
|
||||
|
||||
@staticmethod
|
||||
def sort_groups(_groups_dict):
|
||||
sorted_dict = collections.OrderedDict()
|
||||
|
||||
# make sure won't affect any dictionary as pointer
|
||||
groups_dict = copy.deepcopy(_groups_dict)
|
||||
last_order = None
|
||||
if None in groups_dict:
|
||||
last_order = groups_dict.pop(None)
|
||||
|
||||
for key in sorted(groups_dict):
|
||||
sorted_dict[key] = groups_dict[key]
|
||||
|
||||
if last_order is not None:
|
||||
sorted_dict[None] = last_order
|
||||
|
||||
return sorted_dict
|
||||
|
||||
@staticmethod
|
||||
def parse_group_str(groups_str=None, group_range=None):
|
||||
if groups_str is None:
|
||||
groups_str = os.environ.get("PYBLISH_GROUP_SETTING")
|
||||
|
||||
if groups_str is None:
|
||||
return OrderGroups.sort_groups(OrderGroups.default_groups)
|
||||
|
||||
items = groups_str.split(",")
|
||||
groups = {}
|
||||
for item in items:
|
||||
if "=" not in item:
|
||||
order = None
|
||||
label = item
|
||||
else:
|
||||
order, label = item.split("=")
|
||||
order = order.strip()
|
||||
if not order:
|
||||
order = None
|
||||
elif order.startswith("<"):
|
||||
order = float(order.replace("<", ""))
|
||||
else:
|
||||
if group_range is None:
|
||||
group_range = OrderGroups.default_group_range
|
||||
print(
|
||||
"Using default Plugin group range \"{}\".".format(
|
||||
OrderGroups.default_group_range
|
||||
)
|
||||
)
|
||||
order = float(order) + float(group_range) / 2
|
||||
|
||||
if order in groups:
|
||||
print((
|
||||
"Order \"{}\" is registered more than once."
|
||||
" Using first found."
|
||||
).format(str(order)))
|
||||
continue
|
||||
|
||||
groups[order] = label
|
||||
|
||||
return OrderGroups.sort_groups(groups)
|
||||
|
||||
@staticmethod
|
||||
def parse_validation_order(validation_order_value=None, group_range=None):
|
||||
if validation_order_value is None:
|
||||
validation_order_value = os.environ.get("PYBLISH_VALIDATION_ORDER")
|
||||
|
||||
if validation_order_value is None:
|
||||
return OrderGroups.default_validation_order
|
||||
|
||||
if group_range is None:
|
||||
group_range = OrderGroups.default_group_range
|
||||
|
||||
group_range_half = float(group_range) / 2
|
||||
|
||||
if isinstance(validation_order_value, numbers.Integral):
|
||||
return validation_order_value + group_range_half
|
||||
|
||||
if validation_order_value.startswith("<"):
|
||||
validation_order_value = float(
|
||||
validation_order_value.replace("<", "")
|
||||
)
|
||||
else:
|
||||
validation_order_value = (
|
||||
float(validation_order_value)
|
||||
+ group_range_half
|
||||
)
|
||||
return validation_order_value
|
||||
|
||||
@staticmethod
|
||||
def parse_group_range(group_range=None):
|
||||
if group_range is None:
|
||||
group_range = os.environ.get("PYBLISH_GROUP_RANGE")
|
||||
|
||||
if group_range is None:
|
||||
return OrderGroups.default_group_range
|
||||
|
||||
if isinstance(group_range, numbers.Integral):
|
||||
return group_range
|
||||
|
||||
return float(group_range)
|
||||
validation_order = pyblish.api.ValidatorOrder + 0.5
|
||||
groups = collections.OrderedDict((
|
||||
(
|
||||
pyblish.api.CollectorOrder + 0.5,
|
||||
{
|
||||
"label": "Collect",
|
||||
"state": "Collecting.."
|
||||
}
|
||||
),
|
||||
(
|
||||
pyblish.api.ValidatorOrder + 0.5,
|
||||
{
|
||||
"label": "Validate",
|
||||
"state": "Validating.."
|
||||
}
|
||||
),
|
||||
(
|
||||
pyblish.api.ExtractorOrder + 0.5,
|
||||
{
|
||||
"label": "Extract",
|
||||
"state": "Extracting.."
|
||||
}
|
||||
),
|
||||
(
|
||||
pyblish.api.IntegratorOrder + 0.5,
|
||||
{
|
||||
"label": "Integrate",
|
||||
"state": "Integrating.."
|
||||
}
|
||||
),
|
||||
(
|
||||
None,
|
||||
{
|
||||
"label": "Other",
|
||||
"state": "Finishing.."
|
||||
}
|
||||
)
|
||||
))
|
||||
|
||||
|
||||
def env_variable_to_bool(env_key, default=False):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class OverviewView(QtWidgets.QTreeView):
|
|||
toggled = QtCore.Signal(QtCore.QModelIndex, object)
|
||||
show_perspective = QtCore.Signal(QtCore.QModelIndex)
|
||||
|
||||
def __init__(self, animated, parent=None):
|
||||
def __init__(self, parent=None):
|
||||
super(OverviewView, self).__init__(parent)
|
||||
|
||||
self.horizontalScrollBar().hide()
|
||||
|
|
@ -28,8 +28,6 @@ class OverviewView(QtWidgets.QTreeView):
|
|||
self.setHeaderHidden(True)
|
||||
self.setRootIsDecorated(False)
|
||||
self.setIndentation(0)
|
||||
if animated:
|
||||
self.setAnimated(True)
|
||||
|
||||
def event(self, event):
|
||||
if not event.type() == QtCore.QEvent.KeyPress:
|
||||
|
|
|
|||
|
|
@ -143,9 +143,8 @@ class Window(QtWidgets.QDialog):
|
|||
# TODO add parent
|
||||
overview_page = QtWidgets.QWidget()
|
||||
|
||||
overview_instance_view = view.InstanceView(
|
||||
animated=settings.Animated, parent=overview_page
|
||||
)
|
||||
overview_instance_view = view.InstanceView(parent=overview_page)
|
||||
overview_instance_view.setAnimated(settings.Animated)
|
||||
overview_instance_delegate = delegate.InstanceDelegate(
|
||||
parent=overview_instance_view
|
||||
)
|
||||
|
|
@ -156,9 +155,8 @@ class Window(QtWidgets.QDialog):
|
|||
overview_instance_view.setItemDelegate(overview_instance_delegate)
|
||||
overview_instance_view.setModel(instance_sort_proxy)
|
||||
|
||||
overview_plugin_view = view.PluginView(
|
||||
animated=settings.Animated, parent=overview_page
|
||||
)
|
||||
overview_plugin_view = view.PluginView(parent=overview_page)
|
||||
overview_plugin_view.setAnimated(settings.Animated)
|
||||
overview_plugin_delegate = delegate.PluginDelegate(
|
||||
parent=overview_plugin_view
|
||||
)
|
||||
|
|
@ -298,34 +296,6 @@ class Window(QtWidgets.QDialog):
|
|||
self.main_layout.setSpacing(0)
|
||||
self.main_layout.addWidget(main_widget)
|
||||
|
||||
# Display info
|
||||
info_effect = QtWidgets.QGraphicsOpacityEffect(footer_info)
|
||||
footer_info.setGraphicsEffect(info_effect)
|
||||
|
||||
on = QtCore.QPropertyAnimation(info_effect, b"opacity")
|
||||
on.setDuration(0)
|
||||
on.setStartValue(0)
|
||||
on.setEndValue(1)
|
||||
|
||||
off = QtCore.QPropertyAnimation(info_effect, b"opacity")
|
||||
off.setDuration(0)
|
||||
off.setStartValue(1)
|
||||
off.setEndValue(0)
|
||||
|
||||
fade = QtCore.QPropertyAnimation(info_effect, b"opacity")
|
||||
fade.setDuration(500)
|
||||
fade.setStartValue(1.0)
|
||||
fade.setEndValue(0.0)
|
||||
|
||||
animation_info_msg = QtCore.QSequentialAnimationGroup()
|
||||
animation_info_msg.addAnimation(on)
|
||||
animation_info_msg.addPause(50)
|
||||
animation_info_msg.addAnimation(off)
|
||||
animation_info_msg.addPause(50)
|
||||
animation_info_msg.addAnimation(on)
|
||||
animation_info_msg.addPause(2000)
|
||||
animation_info_msg.addAnimation(fade)
|
||||
|
||||
"""Setup
|
||||
|
||||
Widgets are referred to in CSS via their object-name. We
|
||||
|
|
@ -459,6 +429,8 @@ class Window(QtWidgets.QDialog):
|
|||
self.footer_button_validate = footer_button_validate
|
||||
self.footer_button_play = footer_button_play
|
||||
|
||||
self.footer_info = footer_info
|
||||
|
||||
self.overview_instance_view = overview_instance_view
|
||||
self.overview_plugin_view = overview_plugin_view
|
||||
self.plugin_model = plugin_model
|
||||
|
|
@ -468,8 +440,6 @@ class Window(QtWidgets.QDialog):
|
|||
|
||||
self.presets_button = presets_button
|
||||
|
||||
self.animation_info_msg = animation_info_msg
|
||||
|
||||
self.terminal_model = terminal_model
|
||||
self.terminal_proxy = terminal_proxy
|
||||
self.terminal_view = terminal_view
|
||||
|
|
@ -995,6 +965,8 @@ class Window(QtWidgets.QDialog):
|
|||
self.footer_button_stop.setEnabled(True)
|
||||
self.footer_button_play.setEnabled(False)
|
||||
|
||||
self._update_state()
|
||||
|
||||
def on_passed_group(self, order):
|
||||
for group_item in self.instance_model.group_items.values():
|
||||
group_index = self.instance_sort_proxy.mapFromSource(
|
||||
|
|
@ -1023,9 +995,13 @@ class Window(QtWidgets.QDialog):
|
|||
{GroupStates.HasFinished: True},
|
||||
Roles.PublishFlagsRole
|
||||
)
|
||||
self.overview_plugin_view.setAnimated(False)
|
||||
self.overview_plugin_view.collapse(group_index)
|
||||
|
||||
self._update_state()
|
||||
|
||||
def on_was_stopped(self):
|
||||
self.overview_plugin_view.setAnimated(settings.Animated)
|
||||
errored = self.controller.errored
|
||||
if self.controller.collect_state == 0:
|
||||
self.footer_button_play.setEnabled(False)
|
||||
|
|
@ -1049,6 +1025,11 @@ class Window(QtWidgets.QDialog):
|
|||
)
|
||||
self.button_suspend_logs.setEnabled(suspend_log_bool)
|
||||
|
||||
self._update_state()
|
||||
|
||||
if not self.isVisible():
|
||||
self.setVisible(True)
|
||||
|
||||
def on_was_skipped(self, plugin):
|
||||
plugin_item = self.plugin_model.plugin_items[plugin.id]
|
||||
plugin_item.setData(
|
||||
|
|
@ -1057,6 +1038,7 @@ class Window(QtWidgets.QDialog):
|
|||
)
|
||||
|
||||
def on_was_finished(self):
|
||||
self.overview_plugin_view.setAnimated(settings.Animated)
|
||||
self.footer_button_play.setEnabled(False)
|
||||
self.footer_button_validate.setEnabled(False)
|
||||
self.footer_button_reset.setEnabled(True)
|
||||
|
|
@ -1090,6 +1072,7 @@ class Window(QtWidgets.QDialog):
|
|||
)
|
||||
|
||||
self.update_compatibility()
|
||||
self._update_state()
|
||||
|
||||
def on_was_processed(self, result):
|
||||
existing_ids = set(self.instance_model.instance_items.keys())
|
||||
|
|
@ -1120,6 +1103,9 @@ class Window(QtWidgets.QDialog):
|
|||
plugin_item, instance_item
|
||||
)
|
||||
|
||||
if not self.isVisible():
|
||||
self.setVisible(True)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
#
|
||||
# Functions
|
||||
|
|
@ -1168,6 +1154,8 @@ class Window(QtWidgets.QDialog):
|
|||
|
||||
self.controller.validate()
|
||||
|
||||
self._update_state()
|
||||
|
||||
def publish(self):
|
||||
self.info(self.tr("Preparing publish.."))
|
||||
self.footer_button_stop.setEnabled(True)
|
||||
|
|
@ -1179,6 +1167,8 @@ class Window(QtWidgets.QDialog):
|
|||
|
||||
self.controller.publish()
|
||||
|
||||
self._update_state()
|
||||
|
||||
def act(self, plugin_item, action):
|
||||
self.info("%s %s.." % (self.tr("Preparing"), action))
|
||||
|
||||
|
|
@ -1293,6 +1283,9 @@ class Window(QtWidgets.QDialog):
|
|||
#
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _update_state(self):
|
||||
self.footer_info.setText(self.controller.current_state)
|
||||
|
||||
def info(self, message):
|
||||
"""Print user-facing information
|
||||
|
||||
|
|
@ -1300,22 +1293,15 @@ class Window(QtWidgets.QDialog):
|
|||
message (str): Text message for the user
|
||||
|
||||
"""
|
||||
|
||||
info = self.findChild(QtWidgets.QLabel, "FooterInfo")
|
||||
info.setText(message)
|
||||
|
||||
# Include message in terminal
|
||||
self.terminal_model.append([{
|
||||
"label": message,
|
||||
"type": "info"
|
||||
}])
|
||||
|
||||
self.animation_info_msg.stop()
|
||||
self.animation_info_msg.start()
|
||||
|
||||
# TODO(marcus): Should this be configurable? Do we want
|
||||
# the shell to fill up with these messages?
|
||||
util.u_print(message)
|
||||
if settings.PrintInfo:
|
||||
# Print message to console
|
||||
util.u_print(message)
|
||||
|
||||
def warning(self, message):
|
||||
"""Block processing and print warning until user hits "Continue"
|
||||
|
|
|
|||
|
|
@ -11,15 +11,12 @@ from openpype.tools.utils import PlaceholderLineEdit
|
|||
class AppVariantWidget(QtWidgets.QWidget):
|
||||
exec_placeholder = "< Specific path for this machine >"
|
||||
|
||||
def __init__(self, group_label, variant_name, variant_entity, parent):
|
||||
def __init__(
|
||||
self, group_label, variant_name, variant_label, variant_entity, parent
|
||||
):
|
||||
super(AppVariantWidget, self).__init__(parent)
|
||||
|
||||
self.executable_input_widget = None
|
||||
variant_label = variant_entity.label
|
||||
if variant_label is None:
|
||||
parent_entity = variant_entity.parent
|
||||
if hasattr(parent_entity, "get_key_label"):
|
||||
variant_label = parent_entity.get_key_label(variant_name)
|
||||
|
||||
if not variant_label:
|
||||
variant_label = variant_name
|
||||
|
|
@ -107,15 +104,15 @@ class AppVariantWidget(QtWidgets.QWidget):
|
|||
|
||||
|
||||
class AppGroupWidget(QtWidgets.QWidget):
|
||||
def __init__(self, group_entity, parent):
|
||||
def __init__(self, group_entity, group_label, parent, dynamic=False):
|
||||
super(AppGroupWidget, self).__init__(parent)
|
||||
|
||||
variants_entity = group_entity["variants"]
|
||||
valid_variants = {}
|
||||
for key, entity in group_entity["variants"].items():
|
||||
for key, entity in variants_entity.items():
|
||||
if "enabled" not in entity or entity["enabled"].value:
|
||||
valid_variants[key] = entity
|
||||
|
||||
group_label = group_entity.label
|
||||
expading_widget = ExpandingWidget(group_label, self)
|
||||
content_widget = QtWidgets.QWidget(expading_widget)
|
||||
content_layout = QtWidgets.QVBoxLayout(content_widget)
|
||||
|
|
@ -126,8 +123,16 @@ class AppGroupWidget(QtWidgets.QWidget):
|
|||
if "executables" not in variant_entity:
|
||||
continue
|
||||
|
||||
variant_label = variant_entity.label
|
||||
if dynamic and hasattr(variants_entity, "get_key_label"):
|
||||
variant_label = variants_entity.get_key_label(variant_name)
|
||||
|
||||
variant_widget = AppVariantWidget(
|
||||
group_label, variant_name, variant_entity, content_widget
|
||||
group_label,
|
||||
variant_name,
|
||||
variant_label,
|
||||
variant_entity,
|
||||
content_widget
|
||||
)
|
||||
widgets_by_variant_name[variant_name] = variant_widget
|
||||
content_layout.addWidget(variant_widget)
|
||||
|
|
@ -171,6 +176,20 @@ class LocalApplicationsWidgets(QtWidgets.QWidget):
|
|||
|
||||
self.content_layout = layout
|
||||
|
||||
def _filter_group_entity(self, entity):
|
||||
if not entity["enabled"].value:
|
||||
return False
|
||||
|
||||
# Check if has enabled any variant
|
||||
for variant_entity in entity["variants"].values():
|
||||
if (
|
||||
"enabled" not in variant_entity
|
||||
or variant_entity["enabled"].value
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _reset_app_widgets(self):
|
||||
while self.content_layout.count() > 0:
|
||||
item = self.content_layout.itemAt(0)
|
||||
|
|
@ -180,26 +199,35 @@ class LocalApplicationsWidgets(QtWidgets.QWidget):
|
|||
self.content_layout.removeItem(item)
|
||||
self.widgets_by_group_name.clear()
|
||||
|
||||
app_items = {}
|
||||
additional_apps = set()
|
||||
additional_apps_entity = None
|
||||
for key, entity in self.system_settings_entity["applications"].items():
|
||||
# Filter not enabled app groups
|
||||
if not entity["enabled"].value:
|
||||
if key != "additional_apps":
|
||||
app_items[key] = entity
|
||||
continue
|
||||
|
||||
# Check if has enabled any variant
|
||||
enabled_variant = False
|
||||
for variant_entity in entity["variants"].values():
|
||||
if (
|
||||
"enabled" not in variant_entity
|
||||
or variant_entity["enabled"].value
|
||||
):
|
||||
enabled_variant = True
|
||||
break
|
||||
additional_apps_entity = entity
|
||||
for _key, _entity in entity.items():
|
||||
app_items[_key] = _entity
|
||||
additional_apps.add(_key)
|
||||
|
||||
if not enabled_variant:
|
||||
for key, entity in app_items.items():
|
||||
if not self._filter_group_entity(entity):
|
||||
continue
|
||||
|
||||
dynamic = key in additional_apps
|
||||
group_label = None
|
||||
if dynamic and hasattr(additional_apps_entity, "get_key_label"):
|
||||
group_label = additional_apps_entity.get_key_label(key)
|
||||
|
||||
if not group_label:
|
||||
group_label = entity.label
|
||||
if not group_label:
|
||||
group_label = key
|
||||
|
||||
# Create App group specific widget and store it by the key
|
||||
group_widget = AppGroupWidget(entity, self)
|
||||
group_widget = AppGroupWidget(entity, group_label, self, dynamic)
|
||||
if group_widget.widgets_by_variant_name:
|
||||
self.widgets_by_group_name[key] = group_widget
|
||||
self.content_layout.addWidget(group_widget)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from openpype.settings.lib import (
|
|||
)
|
||||
from openpype.tools.settings import CHILD_OFFSET
|
||||
from openpype.api import (
|
||||
Logger,
|
||||
SystemSettings,
|
||||
ProjectSettings
|
||||
)
|
||||
|
|
@ -32,7 +33,7 @@ from .constants import (
|
|||
LOCAL_APPS_KEY
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
class LocalSettingsWidget(QtWidgets.QWidget):
|
||||
|
|
@ -250,6 +251,9 @@ class LocalSettingsWindow(QtWidgets.QWidget):
|
|||
self._settings_widget.update_local_settings(value)
|
||||
|
||||
except Exception as exc:
|
||||
log.warning(
|
||||
"Failed to create local settings window", exc_info=True
|
||||
)
|
||||
error_msg = str(exc)
|
||||
|
||||
crashed = error_msg is not None
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import sys
|
|||
import traceback
|
||||
import contextlib
|
||||
from enum import Enum
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
from openpype.lib import get_openpype_version
|
||||
from openpype.tools.utils import set_style_property
|
||||
from openpype.settings.entities import (
|
||||
SystemSettings,
|
||||
ProjectSettings,
|
||||
|
|
@ -34,7 +36,10 @@ from openpype.settings.entities.op_version_entity import (
|
|||
)
|
||||
|
||||
from openpype.settings import SaveWarningExc
|
||||
from .widgets import ProjectListWidget
|
||||
from .widgets import (
|
||||
ProjectListWidget,
|
||||
VersionAction
|
||||
)
|
||||
from .breadcrumbs_widget import (
|
||||
BreadcrumbsAddressBar,
|
||||
SystemSettingsBreadcrumbs,
|
||||
|
|
@ -86,8 +91,24 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
state_changed = QtCore.Signal()
|
||||
saved = QtCore.Signal(QtWidgets.QWidget)
|
||||
restart_required_trigger = QtCore.Signal()
|
||||
reset_started = QtCore.Signal()
|
||||
reset_finished = QtCore.Signal()
|
||||
full_path_requested = QtCore.Signal(str, str)
|
||||
|
||||
require_restart_label_text = (
|
||||
"Your changes require restart of"
|
||||
" all running OpenPype processes to take affect."
|
||||
)
|
||||
outdated_version_label_text = (
|
||||
"Your settings are loaded from an older version."
|
||||
)
|
||||
source_version_tooltip = "Using settings of current OpenPype version"
|
||||
source_version_tooltip_outdated = (
|
||||
"Please check that all settings are still correct (blue colour\n"
|
||||
"indicates potential changes in the new version) and save your\n"
|
||||
"settings to update them to you current running OpenPype version."
|
||||
)
|
||||
|
||||
def __init__(self, user_role, parent=None):
|
||||
super(SettingsCategoryWidget, self).__init__(parent)
|
||||
|
||||
|
|
@ -98,6 +119,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self._state = CategoryState.Idle
|
||||
|
||||
self._hide_studio_overrides = False
|
||||
self._updating_root = False
|
||||
self._use_version = None
|
||||
self._current_version = get_openpype_version()
|
||||
|
||||
self.ignore_input_changes = IgnoreInputChangesObj(self)
|
||||
|
||||
self.keys = []
|
||||
|
|
@ -183,77 +208,126 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
def initialize_attributes(self):
|
||||
return
|
||||
|
||||
@property
|
||||
def is_modifying_defaults(self):
|
||||
if self.modify_defaults_checkbox is None:
|
||||
return False
|
||||
return self.modify_defaults_checkbox.isChecked()
|
||||
|
||||
def create_ui(self):
|
||||
self.modify_defaults_checkbox = None
|
||||
|
||||
scroll_widget = QtWidgets.QScrollArea(self)
|
||||
scroll_widget.setObjectName("GroupWidget")
|
||||
content_widget = QtWidgets.QWidget(scroll_widget)
|
||||
conf_wrapper_widget = QtWidgets.QWidget(self)
|
||||
configurations_widget = QtWidgets.QWidget(conf_wrapper_widget)
|
||||
|
||||
breadcrumbs_label = QtWidgets.QLabel("Path:", content_widget)
|
||||
breadcrumbs_widget = BreadcrumbsAddressBar(content_widget)
|
||||
# Breadcrumbs/Path widget
|
||||
breadcrumbs_widget = QtWidgets.QWidget(self)
|
||||
breadcrumbs_label = QtWidgets.QLabel("Path:", breadcrumbs_widget)
|
||||
breadcrumbs_bar = BreadcrumbsAddressBar(breadcrumbs_widget)
|
||||
|
||||
breadcrumbs_layout = QtWidgets.QHBoxLayout()
|
||||
refresh_icon = qtawesome.icon("fa.refresh", color="white")
|
||||
refresh_btn = QtWidgets.QPushButton(breadcrumbs_widget)
|
||||
refresh_btn.setIcon(refresh_icon)
|
||||
|
||||
breadcrumbs_layout = QtWidgets.QHBoxLayout(breadcrumbs_widget)
|
||||
breadcrumbs_layout.setContentsMargins(5, 5, 5, 5)
|
||||
breadcrumbs_layout.setSpacing(5)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_label)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_widget)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_label, 0)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_bar, 1)
|
||||
breadcrumbs_layout.addWidget(refresh_btn, 0)
|
||||
|
||||
# Widgets representing settings entities
|
||||
scroll_widget = QtWidgets.QScrollArea(configurations_widget)
|
||||
content_widget = QtWidgets.QWidget(scroll_widget)
|
||||
scroll_widget.setWidgetResizable(True)
|
||||
scroll_widget.setWidget(content_widget)
|
||||
|
||||
content_layout = QtWidgets.QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(3, 3, 3, 3)
|
||||
content_layout.setSpacing(5)
|
||||
content_layout.setAlignment(QtCore.Qt.AlignTop)
|
||||
|
||||
scroll_widget.setWidgetResizable(True)
|
||||
scroll_widget.setWidget(content_widget)
|
||||
# Footer widget
|
||||
footer_widget = QtWidgets.QWidget(self)
|
||||
footer_widget.setObjectName("SettingsFooter")
|
||||
|
||||
refresh_icon = qtawesome.icon("fa.refresh", color="white")
|
||||
refresh_btn = QtWidgets.QPushButton(self)
|
||||
refresh_btn.setIcon(refresh_icon)
|
||||
# Info labels
|
||||
# TODO dynamic labels
|
||||
labels_alignment = QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
|
||||
empty_label = QtWidgets.QLabel(footer_widget)
|
||||
|
||||
footer_layout = QtWidgets.QHBoxLayout()
|
||||
outdated_version_label = QtWidgets.QLabel(
|
||||
self.outdated_version_label_text, footer_widget
|
||||
)
|
||||
outdated_version_label.setToolTip(self.source_version_tooltip_outdated)
|
||||
outdated_version_label.setAlignment(labels_alignment)
|
||||
outdated_version_label.setVisible(False)
|
||||
outdated_version_label.setObjectName("SettingsOutdatedSourceVersion")
|
||||
|
||||
require_restart_label = QtWidgets.QLabel(
|
||||
self.require_restart_label_text, footer_widget
|
||||
)
|
||||
require_restart_label.setAlignment(labels_alignment)
|
||||
require_restart_label.setVisible(False)
|
||||
|
||||
# Label showing source version of loaded settings
|
||||
source_version_label = QtWidgets.QLabel("", footer_widget)
|
||||
source_version_label.setObjectName("SourceVersionLabel")
|
||||
set_style_property(source_version_label, "state", "")
|
||||
source_version_label.setToolTip(self.source_version_tooltip)
|
||||
|
||||
save_btn = QtWidgets.QPushButton("Save", footer_widget)
|
||||
|
||||
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
|
||||
footer_layout.setContentsMargins(5, 5, 5, 5)
|
||||
if self.user_role == "developer":
|
||||
self._add_developer_ui(footer_layout)
|
||||
self._add_developer_ui(footer_layout, footer_widget)
|
||||
|
||||
save_btn = QtWidgets.QPushButton("Save", self)
|
||||
require_restart_label = QtWidgets.QLabel(self)
|
||||
require_restart_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
|
||||
footer_layout.addWidget(refresh_btn, 0)
|
||||
footer_layout.addWidget(empty_label, 1)
|
||||
footer_layout.addWidget(outdated_version_label, 1)
|
||||
footer_layout.addWidget(require_restart_label, 1)
|
||||
footer_layout.addWidget(source_version_label, 0)
|
||||
footer_layout.addWidget(save_btn, 0)
|
||||
|
||||
configurations_layout = QtWidgets.QVBoxLayout()
|
||||
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
|
||||
configurations_layout.setContentsMargins(0, 0, 0, 0)
|
||||
configurations_layout.setSpacing(0)
|
||||
|
||||
configurations_layout.addWidget(scroll_widget, 1)
|
||||
configurations_layout.addLayout(footer_layout, 0)
|
||||
|
||||
conf_wrapper_layout = QtWidgets.QHBoxLayout()
|
||||
conf_wrapper_layout = QtWidgets.QHBoxLayout(conf_wrapper_widget)
|
||||
conf_wrapper_layout.setContentsMargins(0, 0, 0, 0)
|
||||
conf_wrapper_layout.setSpacing(0)
|
||||
conf_wrapper_layout.addLayout(configurations_layout, 1)
|
||||
conf_wrapper_layout.addWidget(configurations_widget, 1)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setSpacing(0)
|
||||
main_layout.addLayout(breadcrumbs_layout, 0)
|
||||
main_layout.addLayout(conf_wrapper_layout, 1)
|
||||
main_layout.addWidget(breadcrumbs_widget, 0)
|
||||
main_layout.addWidget(conf_wrapper_widget, 1)
|
||||
main_layout.addWidget(footer_widget, 0)
|
||||
|
||||
save_btn.clicked.connect(self._save)
|
||||
refresh_btn.clicked.connect(self._on_refresh)
|
||||
breadcrumbs_widget.path_edited.connect(self._on_path_edit)
|
||||
breadcrumbs_bar.path_edited.connect(self._on_path_edit)
|
||||
|
||||
self._require_restart_label = require_restart_label
|
||||
self._outdated_version_label = outdated_version_label
|
||||
self._empty_label = empty_label
|
||||
|
||||
self._is_loaded_version_outdated = False
|
||||
|
||||
self.save_btn = save_btn
|
||||
self.refresh_btn = refresh_btn
|
||||
self.require_restart_label = require_restart_label
|
||||
self._source_version_label = source_version_label
|
||||
|
||||
self.scroll_widget = scroll_widget
|
||||
self.content_layout = content_layout
|
||||
self.content_widget = content_widget
|
||||
self.breadcrumbs_widget = breadcrumbs_widget
|
||||
self.breadcrumbs_bar = breadcrumbs_bar
|
||||
|
||||
self.breadcrumbs_model = None
|
||||
self.refresh_btn = refresh_btn
|
||||
|
||||
self.conf_wrapper_layout = conf_wrapper_layout
|
||||
self.main_layout = main_layout
|
||||
|
||||
|
|
@ -307,22 +381,23 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
"""Change path of widget based on category full path."""
|
||||
pass
|
||||
|
||||
def set_path(self, path):
|
||||
self.breadcrumbs_widget.set_path(path)
|
||||
def change_path(self, path):
|
||||
"""Change path and go to widget."""
|
||||
self.breadcrumbs_bar.change_path(path)
|
||||
|
||||
def _add_developer_ui(self, footer_layout):
|
||||
modify_defaults_widget = QtWidgets.QWidget()
|
||||
modify_defaults_checkbox = QtWidgets.QCheckBox(modify_defaults_widget)
|
||||
def set_path(self, path):
|
||||
"""Called from clicked widget."""
|
||||
self.breadcrumbs_bar.set_path(path)
|
||||
|
||||
def _add_developer_ui(self, footer_layout, footer_widget):
|
||||
modify_defaults_checkbox = QtWidgets.QCheckBox(footer_widget)
|
||||
modify_defaults_checkbox.setChecked(self._hide_studio_overrides)
|
||||
label_widget = QtWidgets.QLabel(
|
||||
"Modify defaults", modify_defaults_widget
|
||||
"Modify defaults", footer_widget
|
||||
)
|
||||
|
||||
modify_defaults_layout = QtWidgets.QHBoxLayout(modify_defaults_widget)
|
||||
modify_defaults_layout.addWidget(label_widget)
|
||||
modify_defaults_layout.addWidget(modify_defaults_checkbox)
|
||||
|
||||
footer_layout.addWidget(modify_defaults_widget, 0)
|
||||
footer_layout.addWidget(label_widget, 0)
|
||||
footer_layout.addWidget(modify_defaults_checkbox, 0)
|
||||
|
||||
modify_defaults_checkbox.stateChanged.connect(
|
||||
self._on_modify_defaults
|
||||
|
|
@ -361,6 +436,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
try:
|
||||
self.entity.save()
|
||||
self._use_version = None
|
||||
|
||||
# NOTE There are relations to previous entities and C++ callbacks
|
||||
# so it is easier to just use new entity and recreate UI but
|
||||
|
|
@ -420,15 +496,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
return
|
||||
|
||||
def _on_require_restart_change(self):
|
||||
value = ""
|
||||
if self.entity.require_restart:
|
||||
value = (
|
||||
"Your changes require restart of"
|
||||
" all running OpenPype processes to take affect."
|
||||
)
|
||||
self.require_restart_label.setText(value)
|
||||
self._update_labels_visibility()
|
||||
|
||||
def reset(self):
|
||||
self.reset_started.emit()
|
||||
self.set_state(CategoryState.Working)
|
||||
|
||||
self._on_reset_start()
|
||||
|
|
@ -444,6 +515,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
widget.deleteLater()
|
||||
|
||||
dialog = None
|
||||
self._updating_root = True
|
||||
source_version = ""
|
||||
try:
|
||||
self._create_root_entity()
|
||||
|
||||
|
|
@ -459,6 +532,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
input_field.set_entity_value()
|
||||
|
||||
self.ignore_input_changes.set_ignore(False)
|
||||
source_version = self.entity.source_version
|
||||
|
||||
except DefaultsNotDefined:
|
||||
dialog = QtWidgets.QMessageBox(self)
|
||||
|
|
@ -502,6 +576,27 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
spacer, layout.rowCount(), 0, 1, layout.columnCount()
|
||||
)
|
||||
|
||||
self._updating_root = False
|
||||
|
||||
# Update source version label
|
||||
state_value = ""
|
||||
tooltip = ""
|
||||
outdated = False
|
||||
if source_version:
|
||||
if source_version != self._current_version:
|
||||
state_value = "different"
|
||||
tooltip = self.source_version_tooltip_outdated
|
||||
outdated = True
|
||||
else:
|
||||
state_value = "same"
|
||||
tooltip = self.source_version_tooltip
|
||||
|
||||
self._is_loaded_version_outdated = outdated
|
||||
self._source_version_label.setText(source_version)
|
||||
self._source_version_label.setToolTip(tooltip)
|
||||
set_style_property(self._source_version_label, "state", state_value)
|
||||
self._update_labels_visibility()
|
||||
|
||||
self.set_state(CategoryState.Idle)
|
||||
|
||||
if dialog:
|
||||
|
|
@ -509,6 +604,37 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self._on_reset_crash()
|
||||
else:
|
||||
self._on_reset_success()
|
||||
self.reset_finished.emit()
|
||||
|
||||
def _on_source_version_change(self, version):
|
||||
if self._updating_root:
|
||||
return
|
||||
|
||||
if version == self._current_version:
|
||||
version = None
|
||||
|
||||
self._use_version = version
|
||||
QtCore.QTimer.singleShot(20, self.reset)
|
||||
|
||||
def add_context_actions(self, menu):
|
||||
if not self.entity or self.is_modifying_defaults:
|
||||
return
|
||||
|
||||
versions = self.entity.get_available_studio_versions(sorted=True)
|
||||
if not versions:
|
||||
return
|
||||
|
||||
submenu = QtWidgets.QMenu("Use settings from version", menu)
|
||||
for version in reversed(versions):
|
||||
action = VersionAction(version, submenu)
|
||||
action.version_triggered.connect(
|
||||
self._on_context_version_trigger
|
||||
)
|
||||
submenu.addAction(action)
|
||||
menu.addMenu(submenu)
|
||||
|
||||
def _on_context_version_trigger(self, version):
|
||||
self._on_source_version_change(version)
|
||||
|
||||
def _on_reset_crash(self):
|
||||
self.save_btn.setEnabled(False)
|
||||
|
|
@ -521,10 +647,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self.save_btn.setEnabled(True)
|
||||
|
||||
if self.breadcrumbs_model is not None:
|
||||
path = self.breadcrumbs_widget.path()
|
||||
self.breadcrumbs_widget.set_path("")
|
||||
path = self.breadcrumbs_bar.path()
|
||||
self.breadcrumbs_bar.set_path("")
|
||||
self.breadcrumbs_model.set_entity(self.entity)
|
||||
self.breadcrumbs_widget.change_path(path)
|
||||
self.breadcrumbs_bar.change_path(path)
|
||||
|
||||
def add_children_gui(self):
|
||||
for child_obj in self.entity.children:
|
||||
|
|
@ -565,10 +691,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
def _save(self):
|
||||
# Don't trigger restart if defaults are modified
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
if self.is_modifying_defaults:
|
||||
require_restart = False
|
||||
else:
|
||||
require_restart = self.entity.require_restart
|
||||
|
|
@ -584,7 +707,29 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
if require_restart:
|
||||
self.restart_required_trigger.emit()
|
||||
self.require_restart_label.setText("")
|
||||
|
||||
def _update_labels_visibility(self):
|
||||
visible_label = None
|
||||
labels = {
|
||||
self._empty_label,
|
||||
self._outdated_version_label,
|
||||
self._require_restart_label,
|
||||
}
|
||||
if self.entity.require_restart:
|
||||
visible_label = self._require_restart_label
|
||||
elif self._is_loaded_version_outdated:
|
||||
visible_label = self._outdated_version_label
|
||||
else:
|
||||
visible_label = self._empty_label
|
||||
|
||||
if visible_label.isVisible():
|
||||
return
|
||||
|
||||
for label in labels:
|
||||
if label is visible_label:
|
||||
visible_label.setVisible(True)
|
||||
else:
|
||||
label.setVisible(False)
|
||||
|
||||
def _on_refresh(self):
|
||||
self.reset()
|
||||
|
|
@ -594,25 +739,29 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
|
||||
|
||||
class SystemWidget(SettingsCategoryWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._actions = []
|
||||
super(SystemWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def contain_category_key(self, category):
|
||||
if category == "system_settings":
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_category_path(self, category, path):
|
||||
self.breadcrumbs_widget.change_path(path)
|
||||
self.breadcrumbs_bar.change_path(path)
|
||||
|
||||
def _create_root_entity(self):
|
||||
self.entity = SystemSettings(set_studio_state=False)
|
||||
self.entity.on_change_callbacks.append(self._on_entity_change)
|
||||
entity = SystemSettings(
|
||||
set_studio_state=False, source_version=self._use_version
|
||||
)
|
||||
entity.on_change_callbacks.append(self._on_entity_change)
|
||||
self.entity = entity
|
||||
try:
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
self.entity.set_defaults_state()
|
||||
if self.is_modifying_defaults:
|
||||
entity.set_defaults_state()
|
||||
else:
|
||||
self.entity.set_studio_state()
|
||||
entity.set_studio_state()
|
||||
|
||||
if self.modify_defaults_checkbox:
|
||||
self.modify_defaults_checkbox.setEnabled(True)
|
||||
|
|
@ -620,16 +769,16 @@ class SystemWidget(SettingsCategoryWidget):
|
|||
if not self.modify_defaults_checkbox:
|
||||
raise
|
||||
|
||||
self.entity.set_defaults_state()
|
||||
entity.set_defaults_state()
|
||||
self.modify_defaults_checkbox.setChecked(True)
|
||||
self.modify_defaults_checkbox.setEnabled(False)
|
||||
|
||||
def ui_tweaks(self):
|
||||
self.breadcrumbs_model = SystemSettingsBreadcrumbs()
|
||||
self.breadcrumbs_widget.set_model(self.breadcrumbs_model)
|
||||
self.breadcrumbs_bar.set_model(self.breadcrumbs_model)
|
||||
|
||||
def _on_modify_defaults(self):
|
||||
if self.modify_defaults_checkbox.isChecked():
|
||||
if self.is_modifying_defaults:
|
||||
if not self.entity.is_in_defaults_state():
|
||||
self.reset()
|
||||
else:
|
||||
|
|
@ -638,6 +787,9 @@ class SystemWidget(SettingsCategoryWidget):
|
|||
|
||||
|
||||
class ProjectWidget(SettingsCategoryWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProjectWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
def contain_category_key(self, category):
|
||||
if category in ("project_settings", "project_anatomy"):
|
||||
return True
|
||||
|
|
@ -651,28 +803,28 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
else:
|
||||
path = category
|
||||
|
||||
self.breadcrumbs_widget.change_path(path)
|
||||
self.breadcrumbs_bar.change_path(path)
|
||||
|
||||
def initialize_attributes(self):
|
||||
self.project_name = None
|
||||
|
||||
def ui_tweaks(self):
|
||||
self.breadcrumbs_model = ProjectSettingsBreadcrumbs()
|
||||
self.breadcrumbs_widget.set_model(self.breadcrumbs_model)
|
||||
self.breadcrumbs_bar.set_model(self.breadcrumbs_model)
|
||||
|
||||
project_list_widget = ProjectListWidget(self)
|
||||
|
||||
self.conf_wrapper_layout.insertWidget(0, project_list_widget, 0)
|
||||
|
||||
project_list_widget.project_changed.connect(self._on_project_change)
|
||||
project_list_widget.version_change_requested.connect(
|
||||
self._on_source_version_change
|
||||
)
|
||||
|
||||
self.project_list_widget = project_list_widget
|
||||
|
||||
def get_project_names(self):
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
if self.is_modifying_defaults:
|
||||
return []
|
||||
return self.project_list_widget.get_project_names()
|
||||
|
||||
|
|
@ -684,6 +836,10 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
if self is saved_tab_widget:
|
||||
return
|
||||
|
||||
def _on_context_version_trigger(self, version):
|
||||
self.project_list_widget.select_project(None)
|
||||
super(ProjectWidget, self)._on_context_version_trigger(version)
|
||||
|
||||
def _on_reset_start(self):
|
||||
self.project_list_widget.refresh()
|
||||
|
||||
|
|
@ -696,32 +852,29 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
super(ProjectWidget, self)._on_reset_success()
|
||||
|
||||
def _set_enabled_project_list(self, enabled):
|
||||
if (
|
||||
enabled
|
||||
and self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
if enabled and self.is_modifying_defaults:
|
||||
enabled = False
|
||||
if self.project_list_widget.isEnabled() != enabled:
|
||||
self.project_list_widget.setEnabled(enabled)
|
||||
|
||||
def _create_root_entity(self):
|
||||
self.entity = ProjectSettings(change_state=False)
|
||||
self.entity.on_change_callbacks.append(self._on_entity_change)
|
||||
entity = ProjectSettings(
|
||||
change_state=False, source_version=self._use_version
|
||||
)
|
||||
entity.on_change_callbacks.append(self._on_entity_change)
|
||||
self.project_list_widget.set_entity(entity)
|
||||
self.entity = entity
|
||||
try:
|
||||
if (
|
||||
self.modify_defaults_checkbox
|
||||
and self.modify_defaults_checkbox.isChecked()
|
||||
):
|
||||
if self.is_modifying_defaults:
|
||||
self.entity.set_defaults_state()
|
||||
|
||||
elif self.project_name is None:
|
||||
self.entity.set_studio_state()
|
||||
|
||||
elif self.project_name == self.entity.project_name:
|
||||
self.entity.set_project_state()
|
||||
else:
|
||||
self.entity.change_project(self.project_name)
|
||||
self.entity.change_project(
|
||||
self.project_name, self._use_version
|
||||
)
|
||||
|
||||
if self.modify_defaults_checkbox:
|
||||
self.modify_defaults_checkbox.setEnabled(True)
|
||||
|
|
@ -754,7 +907,7 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
self.set_state(CategoryState.Idle)
|
||||
|
||||
def _on_modify_defaults(self):
|
||||
if self.modify_defaults_checkbox.isChecked():
|
||||
if self.is_modifying_defaults:
|
||||
self._set_enabled_project_list(False)
|
||||
if not self.entity.is_in_defaults_state():
|
||||
self.reset()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ DEFAULT_PROJECT_LABEL = "< Default >"
|
|||
PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 1
|
||||
PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2
|
||||
PROJECT_IS_SELECTED_ROLE = QtCore.Qt.UserRole + 3
|
||||
PROJECT_VERSION_ROLE = QtCore.Qt.UserRole + 4
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -12,5 +13,6 @@ __all__ = (
|
|||
|
||||
"PROJECT_NAME_ROLE",
|
||||
"PROJECT_IS_ACTIVE_ROLE",
|
||||
"PROJECT_IS_SELECTED_ROLE"
|
||||
"PROJECT_IS_SELECTED_ROLE",
|
||||
"PROJECT_VERSION_ROLE",
|
||||
)
|
||||
|
|
|
|||
186
openpype/tools/settings/settings/search_dialog.py
Normal file
186
openpype/tools/settings/settings/search_dialog.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import re
|
||||
import collections
|
||||
|
||||
from Qt import QtCore, QtWidgets, QtGui
|
||||
|
||||
ENTITY_LABEL_ROLE = QtCore.Qt.UserRole + 1
|
||||
ENTITY_PATH_ROLE = QtCore.Qt.UserRole + 2
|
||||
|
||||
|
||||
def get_entity_children(entity):
|
||||
# TODO find better way how to go through all children
|
||||
if hasattr(entity, "values"):
|
||||
return entity.values()
|
||||
return []
|
||||
|
||||
|
||||
class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
|
||||
"""Filters recursively to regex in all columns"""
|
||||
|
||||
def __init__(self):
|
||||
super(RecursiveSortFilterProxyModel, self).__init__()
|
||||
|
||||
# Note: Recursive filtering was introduced in Qt 5.10.
|
||||
self.setRecursiveFilteringEnabled(True)
|
||||
|
||||
def filterAcceptsRow(self, row, parent):
|
||||
if not parent.isValid():
|
||||
return False
|
||||
|
||||
regex = self.filterRegExp()
|
||||
if not regex.isEmpty() and regex.isValid():
|
||||
pattern = regex.pattern()
|
||||
compiled_regex = re.compile(pattern)
|
||||
source_model = self.sourceModel()
|
||||
|
||||
# Check current index itself in all columns
|
||||
source_index = source_model.index(row, 0, parent)
|
||||
if source_index.isValid():
|
||||
for role in (ENTITY_PATH_ROLE, ENTITY_LABEL_ROLE):
|
||||
value = source_model.data(source_index, role)
|
||||
if value and compiled_regex.search(value):
|
||||
return True
|
||||
return False
|
||||
|
||||
return super(
|
||||
RecursiveSortFilterProxyModel, self
|
||||
).filterAcceptsRow(row, parent)
|
||||
|
||||
|
||||
class SearchEntitiesDialog(QtWidgets.QDialog):
|
||||
path_clicked = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent):
|
||||
super(SearchEntitiesDialog, self).__init__(parent=parent)
|
||||
|
||||
self.setWindowTitle("Search Settings")
|
||||
|
||||
filter_edit = QtWidgets.QLineEdit(self)
|
||||
filter_edit.setPlaceholderText("Search...")
|
||||
|
||||
model = EntityTreeModel()
|
||||
proxy = RecursiveSortFilterProxyModel()
|
||||
proxy.setSourceModel(model)
|
||||
proxy.setDynamicSortFilter(True)
|
||||
|
||||
view = QtWidgets.QTreeView(self)
|
||||
view.setAllColumnsShowFocus(True)
|
||||
view.setSortingEnabled(True)
|
||||
view.setModel(proxy)
|
||||
model.setColumnCount(3)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(filter_edit)
|
||||
layout.addWidget(view)
|
||||
|
||||
filter_changed_timer = QtCore.QTimer()
|
||||
filter_changed_timer.setInterval(200)
|
||||
|
||||
view.selectionModel().selectionChanged.connect(
|
||||
self._on_selection_change
|
||||
)
|
||||
filter_changed_timer.timeout.connect(self._on_filter_timer)
|
||||
filter_edit.textChanged.connect(self._on_filter_changed)
|
||||
|
||||
self._filter_edit = filter_edit
|
||||
self._model = model
|
||||
self._proxy = proxy
|
||||
self._view = view
|
||||
self._filter_changed_timer = filter_changed_timer
|
||||
|
||||
self._first_show = True
|
||||
|
||||
def set_root_entity(self, entity):
|
||||
self._model.set_root_entity(entity)
|
||||
self._view.resizeColumnToContents(0)
|
||||
|
||||
def showEvent(self, event):
|
||||
super(SearchEntitiesDialog, self).showEvent(event)
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self.resize(700, 500)
|
||||
|
||||
def _on_filter_changed(self, txt):
|
||||
self._filter_changed_timer.start()
|
||||
|
||||
def _on_filter_timer(self):
|
||||
text = self._filter_edit.text()
|
||||
self._proxy.setFilterRegExp(text)
|
||||
|
||||
# WARNING This expanding and resizing is relatively slow.
|
||||
self._view.expandAll()
|
||||
self._view.resizeColumnToContents(0)
|
||||
|
||||
def _on_selection_change(self):
|
||||
current = self._view.currentIndex()
|
||||
path = current.data(ENTITY_PATH_ROLE)
|
||||
self.path_clicked.emit(path)
|
||||
|
||||
|
||||
class EntityTreeModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EntityTreeModel, self).__init__(*args, **kwargs)
|
||||
self.setColumnCount(3)
|
||||
|
||||
def data(self, index, role=None):
|
||||
if role is None:
|
||||
role = QtCore.Qt.DisplayRole
|
||||
|
||||
col = index.column()
|
||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||
if col == 0:
|
||||
pass
|
||||
elif col == 1:
|
||||
role = ENTITY_LABEL_ROLE
|
||||
elif col == 2:
|
||||
role = ENTITY_PATH_ROLE
|
||||
|
||||
if col > 0:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
return super(EntityTreeModel, self).data(index, role)
|
||||
|
||||
def flags(self, index):
|
||||
if index.column() > 0:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
return super(EntityTreeModel, self).flags(index)
|
||||
|
||||
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if section == 0:
|
||||
return "Key"
|
||||
elif section == 1:
|
||||
return "Label"
|
||||
elif section == 2:
|
||||
return "Path"
|
||||
return ""
|
||||
return super(EntityTreeModel, self).headerData(
|
||||
section, orientation, role
|
||||
)
|
||||
|
||||
def set_root_entity(self, root_entity):
|
||||
parent = self.invisibleRootItem()
|
||||
parent.removeRows(0, parent.rowCount())
|
||||
if not root_entity:
|
||||
return
|
||||
|
||||
# We don't want to see the root entity so we directly add its children
|
||||
fill_queue = collections.deque()
|
||||
fill_queue.append((root_entity, parent))
|
||||
cols = self.columnCount()
|
||||
while fill_queue:
|
||||
parent_entity, parent_item = fill_queue.popleft()
|
||||
child_items = []
|
||||
for child in get_entity_children(parent_entity):
|
||||
label = child.label
|
||||
path = child.path
|
||||
key = path.split("/")[-1]
|
||||
item = QtGui.QStandardItem(key)
|
||||
item.setEditable(False)
|
||||
item.setData(label, ENTITY_LABEL_ROLE)
|
||||
item.setData(path, ENTITY_PATH_ROLE)
|
||||
item.setColumnCount(cols)
|
||||
child_items.append(item)
|
||||
fill_queue.append((child, item))
|
||||
|
||||
if child_items:
|
||||
parent_item.appendRows(child_items)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import copy
|
||||
import uuid
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from avalon.vendor import qtawesome
|
||||
from avalon.mongodb import (
|
||||
|
|
@ -12,8 +13,12 @@ from openpype.tools.utils.widgets import ImageButton
|
|||
from openpype.tools.utils.lib import paint_image_with_color
|
||||
|
||||
from openpype.widgets.nice_checkbox import NiceCheckbox
|
||||
from openpype.tools.utils import PlaceholderLineEdit
|
||||
from openpype.settings.lib import get_system_settings
|
||||
from openpype.tools.utils import (
|
||||
PlaceholderLineEdit,
|
||||
DynamicQThread
|
||||
)
|
||||
from openpype.settings.lib import find_closest_version_for_projects
|
||||
from openpype.lib import get_openpype_version
|
||||
from .images import (
|
||||
get_pixmap,
|
||||
get_image
|
||||
|
|
@ -21,11 +26,40 @@ from .images import (
|
|||
from .constants import (
|
||||
DEFAULT_PROJECT_LABEL,
|
||||
PROJECT_NAME_ROLE,
|
||||
PROJECT_VERSION_ROLE,
|
||||
PROJECT_IS_ACTIVE_ROLE,
|
||||
PROJECT_IS_SELECTED_ROLE
|
||||
)
|
||||
|
||||
|
||||
class SettingsTabWidget(QtWidgets.QTabWidget):
|
||||
context_menu_requested = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SettingsTabWidget, self).__init__(*args, **kwargs)
|
||||
self._right_click_tab_idx = None
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
super(SettingsTabWidget, self).mousePressEvent(event)
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
tab_bar = self.tabBar()
|
||||
pos = tab_bar.mapFromGlobal(event.globalPos())
|
||||
tab_idx = tab_bar.tabAt(pos)
|
||||
if tab_idx < 0:
|
||||
tab_idx = None
|
||||
self._right_click_tab_idx = tab_idx
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
super(SettingsTabWidget, self).mouseReleaseEvent(event)
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
tab_bar = self.tabBar()
|
||||
pos = tab_bar.mapFromGlobal(event.globalPos())
|
||||
tab_idx = tab_bar.tabAt(pos)
|
||||
if tab_idx == self._right_click_tab_idx:
|
||||
self.context_menu_requested.emit(tab_idx)
|
||||
self._right_click_tab = None
|
||||
|
||||
|
||||
class CompleterFilter(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CompleterFilter, self).__init__(*args, **kwargs)
|
||||
|
|
@ -603,7 +637,7 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
|
|||
message = "You have unsaved changes. What do you want to do with them?"
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
super(UnsavedChangesDialog, self).__init__(parent)
|
||||
message_label = QtWidgets.QLabel(self.message)
|
||||
|
||||
btns_widget = QtWidgets.QWidget(self)
|
||||
|
|
@ -735,19 +769,65 @@ class SettingsNiceCheckbox(NiceCheckbox):
|
|||
|
||||
|
||||
class ProjectModel(QtGui.QStandardItemModel):
|
||||
_update_versions = QtCore.Signal()
|
||||
|
||||
def __init__(self, only_active, *args, **kwargs):
|
||||
super(ProjectModel, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setColumnCount(2)
|
||||
|
||||
self.dbcon = None
|
||||
|
||||
self._only_active = only_active
|
||||
self._default_item = None
|
||||
self._items_by_name = {}
|
||||
self._versions_by_project = {}
|
||||
|
||||
colors = get_objected_colors()
|
||||
font_color = colors["font"].get_qcolor()
|
||||
font_color.setAlpha(67)
|
||||
self._version_font_color = font_color
|
||||
self._current_version = get_openpype_version()
|
||||
|
||||
self._version_refresh_threads = []
|
||||
self._version_refresh_id = None
|
||||
|
||||
self._update_versions.connect(self._on_update_versions_signal)
|
||||
|
||||
def _on_update_versions_signal(self):
|
||||
for project_name, version in self._versions_by_project.items():
|
||||
if project_name is None:
|
||||
item = self._default_item
|
||||
else:
|
||||
item = self._items_by_name.get(project_name)
|
||||
|
||||
if item and version != self._current_version:
|
||||
item.setData(version, PROJECT_VERSION_ROLE)
|
||||
|
||||
def _fetch_settings_versions(self):
|
||||
"""Used versions per project are loaded in thread to not stuck UI."""
|
||||
version_refresh_id = self._version_refresh_id
|
||||
all_project_names = list(self._items_by_name.keys())
|
||||
all_project_names.append(None)
|
||||
closest_by_project_name = find_closest_version_for_projects(
|
||||
all_project_names
|
||||
)
|
||||
if self._version_refresh_id == version_refresh_id:
|
||||
self._versions_by_project = closest_by_project_name
|
||||
self._update_versions.emit()
|
||||
|
||||
def flags(self, index):
|
||||
if index.column() == 1:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
return super(ProjectModel, self).flags(index)
|
||||
|
||||
def set_dbcon(self, dbcon):
|
||||
self.dbcon = dbcon
|
||||
|
||||
def refresh(self):
|
||||
# Change id of versions refresh
|
||||
self._version_refresh_id = uuid.uuid4()
|
||||
|
||||
new_items = []
|
||||
if self._default_item is None:
|
||||
item = QtGui.QStandardItem(DEFAULT_PROJECT_LABEL)
|
||||
|
|
@ -757,6 +837,7 @@ class ProjectModel(QtGui.QStandardItemModel):
|
|||
new_items.append(item)
|
||||
self._default_item = item
|
||||
|
||||
self._default_item.setData("", PROJECT_VERSION_ROLE)
|
||||
project_names = set()
|
||||
if self.dbcon is not None:
|
||||
for project_doc in self.dbcon.projects(
|
||||
|
|
@ -776,6 +857,7 @@ class ProjectModel(QtGui.QStandardItemModel):
|
|||
is_active = project_doc.get("data", {}).get("active", True)
|
||||
item.setData(project_name, PROJECT_NAME_ROLE)
|
||||
item.setData(is_active, PROJECT_IS_ACTIVE_ROLE)
|
||||
item.setData("", PROJECT_VERSION_ROLE)
|
||||
item.setData(False, PROJECT_IS_SELECTED_ROLE)
|
||||
|
||||
if not is_active:
|
||||
|
|
@ -792,15 +874,87 @@ class ProjectModel(QtGui.QStandardItemModel):
|
|||
if new_items:
|
||||
root_item.appendRows(new_items)
|
||||
|
||||
# Fetch versions per project in thread
|
||||
thread = DynamicQThread(self._fetch_settings_versions)
|
||||
self._version_refresh_threads.append(thread)
|
||||
thread.start()
|
||||
|
||||
class ProjectListView(QtWidgets.QListView):
|
||||
# Cleanup done threads
|
||||
for thread in tuple(self._version_refresh_threads):
|
||||
if thread.isFinished():
|
||||
self._version_refresh_threads.remove(thread)
|
||||
|
||||
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||
if index.column() == 1:
|
||||
if role == QtCore.Qt.TextAlignmentRole:
|
||||
return QtCore.Qt.AlignRight
|
||||
if role == QtCore.Qt.ForegroundRole:
|
||||
return self._version_font_color
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||
role = PROJECT_VERSION_ROLE
|
||||
|
||||
return super(ProjectModel, self).data(index, role)
|
||||
|
||||
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||
if index.column() == 1:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||
role = PROJECT_VERSION_ROLE
|
||||
return super(ProjectModel, self).setData(index, value, role)
|
||||
|
||||
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if section == 0:
|
||||
return "Project name"
|
||||
|
||||
elif section == 1:
|
||||
return "Used version"
|
||||
return ""
|
||||
return super(ProjectModel, self).headerData(
|
||||
section, orientation, role
|
||||
)
|
||||
|
||||
|
||||
class VersionAction(QtWidgets.QAction):
|
||||
version_triggered = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, version, *args, **kwargs):
|
||||
super(VersionAction, self).__init__(version, *args, **kwargs)
|
||||
self._version = version
|
||||
self.triggered.connect(self._on_trigger)
|
||||
|
||||
def _on_trigger(self):
|
||||
self.version_triggered.emit(self._version)
|
||||
|
||||
|
||||
class ProjectView(QtWidgets.QTreeView):
|
||||
left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
|
||||
right_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProjectView, self).__init__(*args, **kwargs)
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.setIndentation(0)
|
||||
|
||||
# Do not allow editing
|
||||
self.setEditTriggers(
|
||||
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
|
||||
)
|
||||
# Do not automatically handle selection
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
index = self.indexAt(event.pos())
|
||||
self.left_mouse_released_at.emit(index)
|
||||
super(ProjectListView, self).mouseReleaseEvent(event)
|
||||
|
||||
elif event.button() == QtCore.Qt.RightButton:
|
||||
index = self.indexAt(event.pos())
|
||||
self.right_mouse_released_at.emit(index)
|
||||
|
||||
super(ProjectView, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
|
||||
|
|
@ -846,18 +1000,21 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
|
|||
|
||||
class ProjectListWidget(QtWidgets.QWidget):
|
||||
project_changed = QtCore.Signal()
|
||||
version_change_requested = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent, only_active=False):
|
||||
self._parent = parent
|
||||
|
||||
self._entity = None
|
||||
self.current_project = None
|
||||
|
||||
super(ProjectListWidget, self).__init__(parent)
|
||||
self.setObjectName("ProjectListWidget")
|
||||
|
||||
label_widget = QtWidgets.QLabel("Projects")
|
||||
content_frame = QtWidgets.QFrame(self)
|
||||
content_frame.setObjectName("ProjectListContentWidget")
|
||||
|
||||
project_list = ProjectListView(self)
|
||||
project_list = ProjectView(content_frame)
|
||||
project_model = ProjectModel(only_active)
|
||||
project_proxy = ProjectSortFilterProxy()
|
||||
|
||||
|
|
@ -865,33 +1022,37 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
project_proxy.setSourceModel(project_model)
|
||||
project_list.setModel(project_proxy)
|
||||
|
||||
# Do not allow editing
|
||||
project_list.setEditTriggers(
|
||||
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
|
||||
)
|
||||
# Do not automatically handle selection
|
||||
project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
content_layout = QtWidgets.QVBoxLayout(content_frame)
|
||||
content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
content_layout.setSpacing(0)
|
||||
content_layout.addWidget(project_list, 1)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(3)
|
||||
layout.addWidget(label_widget, 0)
|
||||
layout.addWidget(project_list, 1)
|
||||
inactive_chk = None
|
||||
if not only_active:
|
||||
checkbox_wrapper = QtWidgets.QWidget(content_frame)
|
||||
checkbox_wrapper.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
if only_active:
|
||||
inactive_chk = None
|
||||
else:
|
||||
inactive_chk = QtWidgets.QCheckBox(" Show Inactive Projects ")
|
||||
inactive_chk = QtWidgets.QCheckBox(
|
||||
"Show Inactive Projects", checkbox_wrapper
|
||||
)
|
||||
inactive_chk.setChecked(not project_proxy.is_filter_enabled())
|
||||
|
||||
layout.addSpacing(5)
|
||||
layout.addWidget(inactive_chk, 0)
|
||||
layout.addSpacing(5)
|
||||
wrapper_layout = QtWidgets.QHBoxLayout(checkbox_wrapper)
|
||||
wrapper_layout.addWidget(inactive_chk, 1)
|
||||
|
||||
content_layout.addWidget(checkbox_wrapper, 0)
|
||||
|
||||
inactive_chk.stateChanged.connect(self.on_inactive_vis_changed)
|
||||
|
||||
project_list.left_mouse_released_at.connect(self.on_item_clicked)
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
# Margins '3' are matching to configurables widget scroll area on right
|
||||
layout.setContentsMargins(5, 3, 3, 3)
|
||||
layout.addWidget(content_frame, 1)
|
||||
|
||||
self._default_project_item = None
|
||||
project_list.left_mouse_released_at.connect(self.on_item_clicked)
|
||||
project_list.right_mouse_released_at.connect(
|
||||
self._on_item_right_clicked
|
||||
)
|
||||
|
||||
self.project_list = project_list
|
||||
self.project_proxy = project_proxy
|
||||
|
|
@ -900,10 +1061,46 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
|
||||
self.dbcon = None
|
||||
|
||||
def on_item_clicked(self, new_index):
|
||||
new_project_name = new_index.data(QtCore.Qt.DisplayRole)
|
||||
if new_project_name is None:
|
||||
def set_entity(self, entity):
|
||||
self._entity = entity
|
||||
|
||||
def _on_item_right_clicked(self, index):
|
||||
if not index.isValid():
|
||||
return
|
||||
project_name = index.data(PROJECT_NAME_ROLE)
|
||||
if project_name is None:
|
||||
project_name = DEFAULT_PROJECT_LABEL
|
||||
|
||||
if self.current_project != project_name:
|
||||
self.on_item_clicked(index)
|
||||
|
||||
if self.current_project != project_name:
|
||||
return
|
||||
|
||||
if not self._entity:
|
||||
return
|
||||
|
||||
versions = self._entity.get_available_source_versions(sorted=True)
|
||||
if not versions:
|
||||
return
|
||||
|
||||
menu = QtWidgets.QMenu(self)
|
||||
submenu = QtWidgets.QMenu("Use settings from version", menu)
|
||||
for version in reversed(versions):
|
||||
action = VersionAction(version, submenu)
|
||||
action.version_triggered.connect(
|
||||
self.version_change_requested
|
||||
)
|
||||
submenu.addAction(action)
|
||||
menu.addMenu(submenu)
|
||||
menu.exec_(QtGui.QCursor.pos())
|
||||
|
||||
def on_item_clicked(self, new_index):
|
||||
if not new_index.isValid():
|
||||
return
|
||||
new_project_name = new_index.data(PROJECT_NAME_ROLE)
|
||||
if new_project_name is None:
|
||||
new_project_name = DEFAULT_PROJECT_LABEL
|
||||
|
||||
if self.current_project == new_project_name:
|
||||
return
|
||||
|
|
@ -963,12 +1160,30 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
index = model.indexFromItem(found_items[0])
|
||||
model.setData(index, True, PROJECT_IS_SELECTED_ROLE)
|
||||
|
||||
index = proxy.mapFromSource(index)
|
||||
src_indexes = []
|
||||
col_count = model.columnCount()
|
||||
if col_count > 1:
|
||||
for col in range(col_count):
|
||||
src_indexes.append(
|
||||
model.index(index.row(), col, index.parent())
|
||||
)
|
||||
dst_indexes = []
|
||||
for index in src_indexes:
|
||||
dst_indexes.append(proxy.mapFromSource(index))
|
||||
|
||||
self.project_list.selectionModel().clear()
|
||||
self.project_list.selectionModel().setCurrentIndex(
|
||||
index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent
|
||||
)
|
||||
selection_model = self.project_list.selectionModel()
|
||||
selection_model.clear()
|
||||
|
||||
first = True
|
||||
for index in dst_indexes:
|
||||
if first:
|
||||
selection_model.setCurrentIndex(
|
||||
index,
|
||||
QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent
|
||||
)
|
||||
first = False
|
||||
continue
|
||||
selection_model.select(index, QtCore.QItemSelectionModel.Select)
|
||||
|
||||
def get_project_names(self):
|
||||
output = []
|
||||
|
|
@ -980,7 +1195,7 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
def refresh(self):
|
||||
selected_project = None
|
||||
for index in self.project_list.selectedIndexes():
|
||||
selected_project = index.data(QtCore.Qt.DisplayRole)
|
||||
selected_project = index.data(PROJECT_NAME_ROLE)
|
||||
break
|
||||
|
||||
mongo_url = os.environ["OPENPYPE_MONGO"]
|
||||
|
|
@ -1008,5 +1223,6 @@ class ProjectListWidget(QtWidgets.QWidget):
|
|||
self.select_project(selected_project)
|
||||
|
||||
self.current_project = self.project_list.currentIndex().data(
|
||||
QtCore.Qt.DisplayRole
|
||||
PROJECT_NAME_ROLE
|
||||
)
|
||||
self.project_list.resizeColumnToContents(0)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ from .categories import (
|
|||
SystemWidget,
|
||||
ProjectWidget
|
||||
)
|
||||
from .widgets import ShadowWidget, RestartDialog
|
||||
from .widgets import (
|
||||
ShadowWidget,
|
||||
RestartDialog,
|
||||
SettingsTabWidget
|
||||
)
|
||||
from .search_dialog import SearchEntitiesDialog
|
||||
from openpype import style
|
||||
|
||||
from openpype.lib import is_admin_password_required
|
||||
|
|
@ -34,7 +39,7 @@ class MainWidget(QtWidgets.QWidget):
|
|||
self.setStyleSheet(stylesheet)
|
||||
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
|
||||
|
||||
header_tab_widget = QtWidgets.QTabWidget(parent=self)
|
||||
header_tab_widget = SettingsTabWidget(parent=self)
|
||||
|
||||
studio_widget = SystemWidget(user_role, header_tab_widget)
|
||||
project_widget = ProjectWidget(user_role, header_tab_widget)
|
||||
|
|
@ -54,19 +59,31 @@ class MainWidget(QtWidgets.QWidget):
|
|||
|
||||
self.setLayout(layout)
|
||||
|
||||
search_dialog = SearchEntitiesDialog(self)
|
||||
|
||||
self._shadow_widget = ShadowWidget("Working...", self)
|
||||
self._shadow_widget.setVisible(False)
|
||||
|
||||
header_tab_widget.currentChanged.connect(self._on_tab_changed)
|
||||
search_dialog.path_clicked.connect(self._on_search_path_clicked)
|
||||
|
||||
for tab_widget in tab_widgets:
|
||||
tab_widget.saved.connect(self._on_tab_save)
|
||||
tab_widget.state_changed.connect(self._on_state_change)
|
||||
tab_widget.restart_required_trigger.connect(
|
||||
self._on_restart_required
|
||||
)
|
||||
tab_widget.reset_started.connect(self._on_reset_started)
|
||||
tab_widget.reset_started.connect(self._on_reset_finished)
|
||||
tab_widget.full_path_requested.connect(self._on_full_path_request)
|
||||
|
||||
header_tab_widget.context_menu_requested.connect(
|
||||
self._on_context_menu_request
|
||||
)
|
||||
|
||||
self._header_tab_widget = header_tab_widget
|
||||
self.tab_widgets = tab_widgets
|
||||
self._search_dialog = search_dialog
|
||||
|
||||
def _on_tab_save(self, source_widget):
|
||||
for tab_widget in self.tab_widgets:
|
||||
|
|
@ -100,6 +117,18 @@ class MainWidget(QtWidgets.QWidget):
|
|||
tab_widget.set_category_path(category, path)
|
||||
break
|
||||
|
||||
def _on_context_menu_request(self, tab_idx):
|
||||
widget = self._header_tab_widget.widget(tab_idx)
|
||||
if not widget:
|
||||
return
|
||||
|
||||
menu = QtWidgets.QMenu(self)
|
||||
widget.add_context_actions(menu)
|
||||
if menu.actions():
|
||||
result = menu.exec_(QtGui.QCursor.pos())
|
||||
if result is not None:
|
||||
self._header_tab_widget.setCurrentIndex(tab_idx)
|
||||
|
||||
def showEvent(self, event):
|
||||
super(MainWidget, self).showEvent(event)
|
||||
if self._reset_on_show:
|
||||
|
|
@ -150,6 +179,21 @@ class MainWidget(QtWidgets.QWidget):
|
|||
for tab_widget in self.tab_widgets:
|
||||
tab_widget.reset()
|
||||
|
||||
def _update_search_dialog(self, clear=False):
|
||||
if self._search_dialog.isVisible():
|
||||
entity = None
|
||||
if not clear:
|
||||
widget = self._header_tab_widget.currentWidget()
|
||||
entity = widget.entity
|
||||
self._search_dialog.set_root_entity(entity)
|
||||
|
||||
def _on_tab_changed(self):
|
||||
self._update_search_dialog()
|
||||
|
||||
def _on_search_path_clicked(self, path):
|
||||
widget = self._header_tab_widget.currentWidget()
|
||||
widget.change_path(path)
|
||||
|
||||
def _on_restart_required(self):
|
||||
# Don't show dialog if there are not registered slots for
|
||||
# `trigger_restart` signal.
|
||||
|
|
@ -164,3 +208,26 @@ class MainWidget(QtWidgets.QWidget):
|
|||
result = dialog.exec_()
|
||||
if result == 1:
|
||||
self.trigger_restart.emit()
|
||||
|
||||
def _on_reset_started(self):
|
||||
widget = self.sender()
|
||||
current_widget = self._header_tab_widget.currentWidget()
|
||||
if current_widget is widget:
|
||||
self._update_search_dialog(True)
|
||||
|
||||
def _on_reset_finished(self):
|
||||
widget = self.sender()
|
||||
current_widget = self._header_tab_widget.currentWidget()
|
||||
if current_widget is widget:
|
||||
self._update_search_dialog()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.matches(QtGui.QKeySequence.Find):
|
||||
# todo: search in all widgets (or in active)?
|
||||
widget = self._header_tab_widget.currentWidget()
|
||||
self._search_dialog.show()
|
||||
self._search_dialog.set_root_entity(widget.entity)
|
||||
event.accept()
|
||||
return
|
||||
|
||||
return super(MainWidget, self).keyPressEvent(event)
|
||||
|
|
|
|||
173
openpype/tools/stdout_broker/app.py
Normal file
173
openpype/tools/stdout_broker/app.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import os
|
||||
import sys
|
||||
import threading
|
||||
import collections
|
||||
import websocket
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from openpype_modules.webserver.host_console_listener import MsgAction
|
||||
from openpype.api import Logger
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
class StdOutBroker:
|
||||
"""
|
||||
Application showing console in Services tray for non python hosts
|
||||
instead of cmd window.
|
||||
"""
|
||||
MAX_LINES = 10000
|
||||
TIMER_TIMEOUT = 0.200
|
||||
|
||||
def __init__(self, host_name):
|
||||
self.host_name = host_name
|
||||
self.webserver_client = None
|
||||
|
||||
self.original_stdout_write = None
|
||||
self.original_stderr_write = None
|
||||
self.log_queue = collections.deque()
|
||||
|
||||
date_str = datetime.now().strftime("%d%m%Y%H%M%S")
|
||||
self.host_id = "{}_{}".format(self.host_name, date_str)
|
||||
|
||||
self._std_available = False
|
||||
self._is_running = False
|
||||
self._catch_std_outputs()
|
||||
|
||||
self._timer = None
|
||||
|
||||
@property
|
||||
def send_to_tray(self):
|
||||
"""Checks if connected to tray and have access to logs."""
|
||||
return self.webserver_client and self._std_available
|
||||
|
||||
def start(self):
|
||||
"""Start app, create and start timer"""
|
||||
if not self._std_available or self._is_running:
|
||||
return
|
||||
self._is_running = True
|
||||
self._create_timer()
|
||||
self._connect_to_tray()
|
||||
|
||||
def stop(self):
|
||||
"""Disconnect from Tray, process last logs"""
|
||||
if not self._is_running:
|
||||
return
|
||||
self._is_running = False
|
||||
self._process_queue()
|
||||
self._disconnect_from_tray()
|
||||
|
||||
def host_connected(self):
|
||||
"""Send to Tray console that host is ready - icon change. """
|
||||
log.info("Host {} connected".format(self.host_id))
|
||||
|
||||
payload = {
|
||||
"host": self.host_id,
|
||||
"action": MsgAction.INITIALIZED,
|
||||
"text": "Integration with {}".format(
|
||||
str.capitalize(self.host_name))
|
||||
}
|
||||
self._send(payload)
|
||||
|
||||
def _create_timer(self):
|
||||
timer = threading.Timer(self.TIMER_TIMEOUT, self._timer_callback)
|
||||
timer.start()
|
||||
self._timer = timer
|
||||
|
||||
def _timer_callback(self):
|
||||
if not self._is_running:
|
||||
return
|
||||
self._process_queue()
|
||||
self._create_timer()
|
||||
|
||||
def _connect_to_tray(self):
|
||||
"""Connect to Tray webserver to pass console output. """
|
||||
if not self._std_available: # not content to log
|
||||
return
|
||||
ws = websocket.WebSocket()
|
||||
webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL")
|
||||
|
||||
if not webserver_url:
|
||||
print("Unknown webserver url, cannot connect to pass log")
|
||||
return
|
||||
|
||||
webserver_url = webserver_url.replace("http", "ws")
|
||||
ws.connect("{}/ws/host_listener".format(webserver_url))
|
||||
self.webserver_client = ws
|
||||
|
||||
payload = {
|
||||
"host": self.host_id,
|
||||
"action": MsgAction.CONNECTING,
|
||||
"text": "Integration with {}".format(
|
||||
str.capitalize(self.host_name))
|
||||
}
|
||||
self._send(payload)
|
||||
|
||||
def _disconnect_from_tray(self):
|
||||
"""Send to Tray that host is closing - remove from Services. """
|
||||
print("Host {} closing".format(self.host_name))
|
||||
if not self.webserver_client:
|
||||
return
|
||||
|
||||
payload = {
|
||||
"host": self.host_id,
|
||||
"action": MsgAction.CLOSE,
|
||||
"text": "Integration with {}".format(
|
||||
str.capitalize(self.host_name))
|
||||
}
|
||||
|
||||
self._send(payload)
|
||||
self.webserver_client.close()
|
||||
|
||||
def _catch_std_outputs(self):
|
||||
"""Redirects standard out and error to own functions"""
|
||||
if sys.stdout:
|
||||
self.original_stdout_write = sys.stdout.write
|
||||
sys.stdout.write = self._my_stdout_write
|
||||
self._std_available = True
|
||||
|
||||
if sys.stderr:
|
||||
self.original_stderr_write = sys.stderr.write
|
||||
sys.stderr.write = self._my_stderr_write
|
||||
self._std_available = True
|
||||
|
||||
def _my_stdout_write(self, text):
|
||||
"""Appends outputted text to queue, keep writing to original stdout"""
|
||||
if self.original_stdout_write is not None:
|
||||
self.original_stdout_write(text)
|
||||
if self.send_to_tray:
|
||||
self.log_queue.append(text)
|
||||
|
||||
def _my_stderr_write(self, text):
|
||||
"""Appends outputted text to queue, keep writing to original stderr"""
|
||||
if self.original_stderr_write is not None:
|
||||
self.original_stderr_write(text)
|
||||
if self.send_to_tray:
|
||||
self.log_queue.append(text)
|
||||
|
||||
def _process_queue(self):
|
||||
"""Sends lines and purges queue"""
|
||||
if not self.send_to_tray:
|
||||
return
|
||||
|
||||
lines = tuple(self.log_queue)
|
||||
self.log_queue.clear()
|
||||
if lines:
|
||||
payload = {
|
||||
"host": self.host_id,
|
||||
"action": MsgAction.ADD,
|
||||
"text": "\n".join(lines)
|
||||
}
|
||||
|
||||
self._send(payload)
|
||||
|
||||
def _send(self, payload):
|
||||
"""Worker method to send to existing websocket connection."""
|
||||
if not self.send_to_tray:
|
||||
return
|
||||
|
||||
try:
|
||||
self.webserver_client.send(json.dumps(payload))
|
||||
except ConnectionResetError: # Tray closed
|
||||
self._connect_to_tray()
|
||||
103
openpype/tools/stdout_broker/window.py
Normal file
103
openpype/tools/stdout_broker/window.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
from avalon import style
|
||||
from Qt import QtWidgets, QtCore
|
||||
import collections
|
||||
import re
|
||||
|
||||
|
||||
class ConsoleDialog(QtWidgets.QDialog):
|
||||
"""Qt dialog to show stdout instead of unwieldy cmd window"""
|
||||
WIDTH = 720
|
||||
HEIGHT = 450
|
||||
MAX_LINES = 10000
|
||||
|
||||
sdict = {
|
||||
r">>> ":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> >>> </span>',
|
||||
r"!!!(?!\sCRI|\sERR)":
|
||||
'<span style="font-weight: bold;color:red"> !!! </span>',
|
||||
r"\-\-\- ":
|
||||
'<span style="font-weight: bold;color:cyan"> --- </span>',
|
||||
r"\*\*\*(?!\sWRN)":
|
||||
'<span style="font-weight: bold;color:#FFD700"> *** </span>',
|
||||
r"\*\*\* WRN":
|
||||
'<span style="font-weight: bold;color:#FFD700"> *** WRN</span>',
|
||||
r" \- ":
|
||||
'<span style="font-weight: bold;color:#FFD700"> - </span>',
|
||||
r"\[ ":
|
||||
'<span style="font-weight: bold;color:#66CDAA">[</span>',
|
||||
r"\]":
|
||||
'<span style="font-weight: bold;color:#66CDAA">]</span>',
|
||||
r"{":
|
||||
'<span style="color:#66CDAA">{',
|
||||
r"}":
|
||||
r"}</span>",
|
||||
r"\(":
|
||||
'<span style="color:#66CDAA">(',
|
||||
r"\)":
|
||||
r")</span>",
|
||||
r"^\.\.\. ":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> ... </span>',
|
||||
r"!!! ERR: ":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> !!! ERR: </span>',
|
||||
r"!!! CRI: ":
|
||||
'<span style="font-weight: bold;color:red"> !!! CRI: </span>',
|
||||
r"(?i)failed":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> FAILED </span>',
|
||||
r"(?i)error":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> ERROR </span>'
|
||||
}
|
||||
|
||||
def __init__(self, text, parent=None):
|
||||
super(ConsoleDialog, self).__init__(parent)
|
||||
layout = QtWidgets.QHBoxLayout(parent)
|
||||
|
||||
plain_text = QtWidgets.QPlainTextEdit(self)
|
||||
plain_text.setReadOnly(True)
|
||||
plain_text.resize(self.WIDTH, self.HEIGHT)
|
||||
plain_text.maximumBlockCount = self.MAX_LINES
|
||||
|
||||
while text:
|
||||
plain_text.appendPlainText(text.popleft().strip())
|
||||
|
||||
layout.addWidget(plain_text)
|
||||
|
||||
self.setWindowTitle("Console output")
|
||||
|
||||
self.plain_text = plain_text
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
self.resize(self.WIDTH, self.HEIGHT)
|
||||
|
||||
def append_text(self, new_text):
|
||||
if isinstance(new_text, str):
|
||||
new_text = collections.deque(new_text.split("\n"))
|
||||
while new_text:
|
||||
text = new_text.popleft()
|
||||
if text:
|
||||
self.plain_text.appendHtml(self.color(text))
|
||||
|
||||
def _multiple_replace(self, text, adict):
|
||||
"""Replace multiple tokens defined in dict.
|
||||
|
||||
Find and replace all occurrences of strings defined in dict is
|
||||
supplied string.
|
||||
|
||||
Args:
|
||||
text (str): string to be searched
|
||||
adict (dict): dictionary with `{'search': 'replace'}`
|
||||
|
||||
Returns:
|
||||
str: string with replaced tokens
|
||||
|
||||
"""
|
||||
for r, v in adict.items():
|
||||
text = re.sub(r, v, text)
|
||||
|
||||
return text
|
||||
|
||||
def color(self, message):
|
||||
"""Color message with html tags. """
|
||||
message = self._multiple_replace(message, self.sdict)
|
||||
|
||||
return message
|
||||
|
|
@ -33,7 +33,8 @@ from openpype.settings import (
|
|||
)
|
||||
from openpype.tools.utils import (
|
||||
WrappedCallbackItem,
|
||||
paint_image_with_color
|
||||
paint_image_with_color,
|
||||
get_warning_pixmap
|
||||
)
|
||||
|
||||
from .pype_info_widget import PypeInfoWidget
|
||||
|
|
@ -76,7 +77,7 @@ class PixmapLabel(QtWidgets.QLabel):
|
|||
super(PixmapLabel, self).resizeEvent(event)
|
||||
|
||||
|
||||
class VersionDialog(QtWidgets.QDialog):
|
||||
class VersionUpdateDialog(QtWidgets.QDialog):
|
||||
restart_requested = QtCore.Signal()
|
||||
ignore_requested = QtCore.Signal()
|
||||
|
||||
|
|
@ -84,7 +85,7 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
_min_height = 130
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(VersionDialog, self).__init__(parent)
|
||||
super(VersionUpdateDialog, self).__init__(parent)
|
||||
|
||||
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
|
|
@ -152,11 +153,11 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
)
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
super(VersionUpdateDialog, self).showEvent(event)
|
||||
self._restart_accepted = False
|
||||
|
||||
def closeEvent(self, event):
|
||||
super().closeEvent(event)
|
||||
super(VersionUpdateDialog, self).closeEvent(event)
|
||||
if self._restart_accepted or self._current_is_higher:
|
||||
return
|
||||
# Trigger ignore requested only if restart was not clicked and current
|
||||
|
|
@ -202,6 +203,63 @@ class VersionDialog(QtWidgets.QDialog):
|
|||
self.accept()
|
||||
|
||||
|
||||
class BuildVersionDialog(QtWidgets.QDialog):
|
||||
"""Build/Installation version is too low for current OpenPype version.
|
||||
|
||||
This dialog tells to user that it's build OpenPype is too old.
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
super(BuildVersionDialog, self).__init__(parent)
|
||||
|
||||
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
self.setWindowTitle("Outdated OpenPype installation")
|
||||
self.setWindowFlags(
|
||||
self.windowFlags()
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
|
||||
top_widget = QtWidgets.QWidget(self)
|
||||
|
||||
warning_pixmap = get_warning_pixmap()
|
||||
warning_icon_label = PixmapLabel(warning_pixmap, top_widget)
|
||||
|
||||
message = (
|
||||
"Your installation of OpenPype <b>does not match minimum"
|
||||
" requirements</b>.<br/><br/>Please update OpenPype installation"
|
||||
" to newer version."
|
||||
)
|
||||
content_label = QtWidgets.QLabel(message, self)
|
||||
|
||||
top_layout = QtWidgets.QHBoxLayout(top_widget)
|
||||
top_layout.setContentsMargins(0, 0, 0, 0)
|
||||
top_layout.addWidget(
|
||||
warning_icon_label, 0,
|
||||
QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter
|
||||
)
|
||||
top_layout.addWidget(content_label, 1)
|
||||
|
||||
footer_widget = QtWidgets.QWidget(self)
|
||||
ok_btn = QtWidgets.QPushButton("I understand", footer_widget)
|
||||
|
||||
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
|
||||
footer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
footer_layout.addStretch(1)
|
||||
footer_layout.addWidget(ok_btn)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.addWidget(top_widget, 0)
|
||||
main_layout.addStretch(1)
|
||||
main_layout.addWidget(footer_widget, 0)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
self.close()
|
||||
|
||||
|
||||
class TrayManager:
|
||||
"""Cares about context of application.
|
||||
|
||||
|
|
@ -272,7 +330,7 @@ class TrayManager:
|
|||
return
|
||||
|
||||
if self._version_dialog is None:
|
||||
self._version_dialog = VersionDialog()
|
||||
self._version_dialog = VersionUpdateDialog()
|
||||
self._version_dialog.restart_requested.connect(
|
||||
self._restart_and_install
|
||||
)
|
||||
|
|
@ -383,6 +441,10 @@ class TrayManager:
|
|||
|
||||
self._validate_settings_defaults()
|
||||
|
||||
if not op_version_control_available():
|
||||
dialog = BuildVersionDialog()
|
||||
dialog.exec_()
|
||||
|
||||
def _validate_settings_defaults(self):
|
||||
valid = True
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,331 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
import re
|
||||
import collections
|
||||
import queue
|
||||
import websocket
|
||||
import json
|
||||
import itertools
|
||||
from datetime import datetime
|
||||
|
||||
from avalon import style
|
||||
from openpype_modules.webserver import host_console_listener
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
|
||||
class ConsoleTrayApp:
|
||||
"""
|
||||
Application showing console in Services tray for non python hosts
|
||||
instead of cmd window.
|
||||
"""
|
||||
callback_queue = None
|
||||
process = None
|
||||
webserver_client = None
|
||||
|
||||
MAX_LINES = 10000
|
||||
|
||||
sdict = {
|
||||
r">>> ":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> >>> </span>',
|
||||
r"!!!(?!\sCRI|\sERR)":
|
||||
'<span style="font-weight: bold;color:red"> !!! </span>',
|
||||
r"\-\-\- ":
|
||||
'<span style="font-weight: bold;color:cyan"> --- </span>',
|
||||
r"\*\*\*(?!\sWRN)":
|
||||
'<span style="font-weight: bold;color:#FFD700"> *** </span>',
|
||||
r"\*\*\* WRN":
|
||||
'<span style="font-weight: bold;color:#FFD700"> *** WRN</span>',
|
||||
r" \- ":
|
||||
'<span style="font-weight: bold;color:#FFD700"> - </span>',
|
||||
r"\[ ":
|
||||
'<span style="font-weight: bold;color:#66CDAA">[</span>',
|
||||
r"\]":
|
||||
'<span style="font-weight: bold;color:#66CDAA">]</span>',
|
||||
r"{":
|
||||
'<span style="color:#66CDAA">{',
|
||||
r"}":
|
||||
r"}</span>",
|
||||
r"\(":
|
||||
'<span style="color:#66CDAA">(',
|
||||
r"\)":
|
||||
r")</span>",
|
||||
r"^\.\.\. ":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> ... </span>',
|
||||
r"!!! ERR: ":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> !!! ERR: </span>',
|
||||
r"!!! CRI: ":
|
||||
'<span style="font-weight: bold;color:red"> !!! CRI: </span>',
|
||||
r"(?i)failed":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> FAILED </span>',
|
||||
r"(?i)error":
|
||||
'<span style="font-weight: bold;color:#EE5C42"> ERROR </span>'
|
||||
}
|
||||
|
||||
def __init__(self, host, launch_method, subprocess_args, is_host_connected,
|
||||
parent=None):
|
||||
self.host = host
|
||||
|
||||
self.initialized = False
|
||||
self.websocket_server = None
|
||||
self.initializing = False
|
||||
self.tray = False
|
||||
self.launch_method = launch_method
|
||||
self.subprocess_args = subprocess_args
|
||||
self.is_host_connected = is_host_connected
|
||||
self.tray_reconnect = True
|
||||
|
||||
self.original_stdout_write = None
|
||||
self.original_stderr_write = None
|
||||
self.new_text = collections.deque()
|
||||
|
||||
timer = QtCore.QTimer()
|
||||
timer.timeout.connect(self.on_timer)
|
||||
timer.setInterval(200)
|
||||
timer.start()
|
||||
|
||||
self.timer = timer
|
||||
|
||||
self.catch_std_outputs()
|
||||
date_str = datetime.now().strftime("%d%m%Y%H%M%S")
|
||||
self.host_id = "{}_{}".format(self.host, date_str)
|
||||
|
||||
def _connect(self):
|
||||
""" Connect to Tray webserver to pass console output. """
|
||||
ws = websocket.WebSocket()
|
||||
webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL")
|
||||
|
||||
if not webserver_url:
|
||||
print("Unknown webserver url, cannot connect to pass log")
|
||||
self.tray_reconnect = False
|
||||
return
|
||||
|
||||
webserver_url = webserver_url.replace("http", "ws")
|
||||
ws.connect("{}/ws/host_listener".format(webserver_url))
|
||||
ConsoleTrayApp.webserver_client = ws
|
||||
|
||||
payload = {
|
||||
"host": self.host_id,
|
||||
"action": host_console_listener.MsgAction.CONNECTING,
|
||||
"text": "Integration with {}".format(str.capitalize(self.host))
|
||||
}
|
||||
self.tray_reconnect = False
|
||||
self._send(payload)
|
||||
|
||||
def _connected(self):
|
||||
""" Send to Tray console that host is ready - icon change. """
|
||||
print("Host {} connected".format(self.host))
|
||||
if not ConsoleTrayApp.webserver_client:
|
||||
return
|
||||
|
||||
payload = {
|
||||
"host": self.host_id,
|
||||
"action": host_console_listener.MsgAction.INITIALIZED,
|
||||
"text": "Integration with {}".format(str.capitalize(self.host))
|
||||
}
|
||||
self.tray_reconnect = False
|
||||
self._send(payload)
|
||||
|
||||
def _close(self):
|
||||
""" Send to Tray that host is closing - remove from Services. """
|
||||
print("Host {} closing".format(self.host))
|
||||
if not ConsoleTrayApp.webserver_client:
|
||||
return
|
||||
|
||||
payload = {
|
||||
"host": self.host_id,
|
||||
"action": host_console_listener.MsgAction.CLOSE,
|
||||
"text": "Integration with {}".format(str.capitalize(self.host))
|
||||
}
|
||||
|
||||
self._send(payload)
|
||||
self.tray_reconnect = False
|
||||
ConsoleTrayApp.webserver_client.close()
|
||||
|
||||
def _send_text_queue(self):
|
||||
"""Sends lines and purges queue"""
|
||||
lines = tuple(self.new_text)
|
||||
self.new_text.clear()
|
||||
|
||||
if lines:
|
||||
self._send_lines(lines)
|
||||
|
||||
def _send_lines(self, lines):
|
||||
""" Send console content. """
|
||||
if not ConsoleTrayApp.webserver_client:
|
||||
return
|
||||
|
||||
payload = {
|
||||
"host": self.host_id,
|
||||
"action": host_console_listener.MsgAction.ADD,
|
||||
"text": "\n".join(lines)
|
||||
}
|
||||
|
||||
self._send(payload)
|
||||
|
||||
def _send(self, payload):
|
||||
""" Worker method to send to existing websocket connection. """
|
||||
if not ConsoleTrayApp.webserver_client:
|
||||
return
|
||||
|
||||
try:
|
||||
ConsoleTrayApp.webserver_client.send(json.dumps(payload))
|
||||
except ConnectionResetError: # Tray closed
|
||||
ConsoleTrayApp.webserver_client = None
|
||||
self.tray_reconnect = True
|
||||
|
||||
def on_timer(self):
|
||||
"""Called periodically to initialize and run function on main thread"""
|
||||
if self.tray_reconnect:
|
||||
self._connect() # reconnect
|
||||
|
||||
self._send_text_queue()
|
||||
|
||||
if not self.initialized:
|
||||
if self.initializing:
|
||||
host_connected = self.is_host_connected()
|
||||
if host_connected is None: # keep trying
|
||||
return
|
||||
elif not host_connected:
|
||||
text = "{} process is not alive. Exiting".format(self.host)
|
||||
print(text)
|
||||
self._send_lines([text])
|
||||
ConsoleTrayApp.websocket_server.stop()
|
||||
sys.exit(1)
|
||||
elif host_connected:
|
||||
self.initialized = True
|
||||
self.initializing = False
|
||||
self._connected()
|
||||
|
||||
return
|
||||
|
||||
ConsoleTrayApp.callback_queue = queue.Queue()
|
||||
self.initializing = True
|
||||
|
||||
self.launch_method(*self.subprocess_args)
|
||||
elif ConsoleTrayApp.callback_queue and \
|
||||
not ConsoleTrayApp.callback_queue.empty():
|
||||
try:
|
||||
callback = ConsoleTrayApp.callback_queue.get(block=False)
|
||||
callback()
|
||||
except queue.Empty:
|
||||
pass
|
||||
elif ConsoleTrayApp.process.poll() is not None:
|
||||
self.exit()
|
||||
|
||||
@classmethod
|
||||
def execute_in_main_thread(cls, func_to_call_from_main_thread):
|
||||
"""Put function to the queue to be picked by 'on_timer'"""
|
||||
if not cls.callback_queue:
|
||||
cls.callback_queue = queue.Queue()
|
||||
cls.callback_queue.put(func_to_call_from_main_thread)
|
||||
|
||||
@classmethod
|
||||
def restart_server(cls):
|
||||
if ConsoleTrayApp.websocket_server:
|
||||
ConsoleTrayApp.websocket_server.stop_server(restart=True)
|
||||
|
||||
# obsolete
|
||||
def exit(self):
|
||||
""" Exit whole application. """
|
||||
self._close()
|
||||
if ConsoleTrayApp.websocket_server:
|
||||
ConsoleTrayApp.websocket_server.stop()
|
||||
if ConsoleTrayApp.process:
|
||||
ConsoleTrayApp.process.kill()
|
||||
ConsoleTrayApp.process.wait()
|
||||
if self.timer:
|
||||
self.timer.stop()
|
||||
QtCore.QCoreApplication.exit()
|
||||
|
||||
def catch_std_outputs(self):
|
||||
"""Redirects standard out and error to own functions"""
|
||||
if not sys.stdout:
|
||||
self.dialog.append_text("Cannot read from stdout!")
|
||||
else:
|
||||
self.original_stdout_write = sys.stdout.write
|
||||
sys.stdout.write = self.my_stdout_write
|
||||
|
||||
if not sys.stderr:
|
||||
self.dialog.append_text("Cannot read from stderr!")
|
||||
else:
|
||||
self.original_stderr_write = sys.stderr.write
|
||||
sys.stderr.write = self.my_stderr_write
|
||||
|
||||
def my_stdout_write(self, text):
|
||||
"""Appends outputted text to queue, keep writing to original stdout"""
|
||||
if self.original_stdout_write is not None:
|
||||
self.original_stdout_write(text)
|
||||
self.new_text.append(text)
|
||||
|
||||
def my_stderr_write(self, text):
|
||||
"""Appends outputted text to queue, keep writing to original stderr"""
|
||||
if self.original_stderr_write is not None:
|
||||
self.original_stderr_write(text)
|
||||
self.new_text.append(text)
|
||||
|
||||
@staticmethod
|
||||
def _multiple_replace(text, adict):
|
||||
"""Replace multiple tokens defined in dict.
|
||||
|
||||
Find and replace all occurrences of strings defined in dict is
|
||||
supplied string.
|
||||
|
||||
Args:
|
||||
text (str): string to be searched
|
||||
adict (dict): dictionary with `{'search': 'replace'}`
|
||||
|
||||
Returns:
|
||||
str: string with replaced tokens
|
||||
|
||||
"""
|
||||
for r, v in adict.items():
|
||||
text = re.sub(r, v, text)
|
||||
|
||||
return text
|
||||
|
||||
@staticmethod
|
||||
def color(message):
|
||||
""" Color message with html tags. """
|
||||
message = ConsoleTrayApp._multiple_replace(message,
|
||||
ConsoleTrayApp.sdict)
|
||||
|
||||
return message
|
||||
|
||||
|
||||
class ConsoleDialog(QtWidgets.QDialog):
|
||||
"""Qt dialog to show stdout instead of unwieldy cmd window"""
|
||||
WIDTH = 720
|
||||
HEIGHT = 450
|
||||
MAX_LINES = 10000
|
||||
|
||||
def __init__(self, text, parent=None):
|
||||
super(ConsoleDialog, self).__init__(parent)
|
||||
layout = QtWidgets.QHBoxLayout(parent)
|
||||
|
||||
plain_text = QtWidgets.QPlainTextEdit(self)
|
||||
plain_text.setReadOnly(True)
|
||||
plain_text.resize(self.WIDTH, self.HEIGHT)
|
||||
plain_text.maximumBlockCount = self.MAX_LINES
|
||||
|
||||
while text:
|
||||
plain_text.appendPlainText(text.popleft().strip())
|
||||
|
||||
layout.addWidget(plain_text)
|
||||
|
||||
self.setWindowTitle("Console output")
|
||||
|
||||
self.plain_text = plain_text
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
self.resize(self.WIDTH, self.HEIGHT)
|
||||
|
||||
def append_text(self, new_text):
|
||||
if isinstance(new_text, str):
|
||||
new_text = collections.deque(new_text.split("\n"))
|
||||
while new_text:
|
||||
text = new_text.popleft()
|
||||
if text:
|
||||
self.plain_text.appendHtml(
|
||||
ConsoleTrayApp.color(text))
|
||||
|
|
@ -10,7 +10,10 @@ from .widgets import (
|
|||
from .error_dialog import ErrorMessageBox
|
||||
from .lib import (
|
||||
WrappedCallbackItem,
|
||||
paint_image_with_color
|
||||
paint_image_with_color,
|
||||
get_warning_pixmap,
|
||||
set_style_property,
|
||||
DynamicQThread,
|
||||
)
|
||||
|
||||
from .models import (
|
||||
|
|
@ -29,6 +32,9 @@ __all__ = (
|
|||
|
||||
"WrappedCallbackItem",
|
||||
"paint_image_with_color",
|
||||
"get_warning_pixmap",
|
||||
"set_style_property",
|
||||
"DynamicQThread",
|
||||
|
||||
"RecursiveSortFilterProxyModel",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ class AssetModel(QtGui.QStandardItemModel):
|
|||
|
||||
self._doc_fetched.connect(self._on_docs_fetched)
|
||||
|
||||
self._items_with_color_by_id = {}
|
||||
self._item_ids_with_color = set()
|
||||
self._items_by_asset_id = {}
|
||||
|
||||
self._last_project_name = None
|
||||
|
|
@ -381,9 +381,11 @@ class AssetModel(QtGui.QStandardItemModel):
|
|||
self._stop_fetch_thread()
|
||||
|
||||
def clear_underlines(self):
|
||||
for asset_id in tuple(self._items_with_color_by_id.keys()):
|
||||
item = self._items_with_color_by_id.pop(asset_id)
|
||||
item.setData(None, ASSET_UNDERLINE_COLORS_ROLE)
|
||||
for asset_id in set(self._item_ids_with_color):
|
||||
self._item_ids_with_color.remove(asset_id)
|
||||
item = self._items_by_asset_id.get(asset_id)
|
||||
if item is not None:
|
||||
item.setData(None, ASSET_UNDERLINE_COLORS_ROLE)
|
||||
|
||||
def set_underline_colors(self, colors_by_asset_id):
|
||||
self.clear_underlines()
|
||||
|
|
@ -393,12 +395,13 @@ class AssetModel(QtGui.QStandardItemModel):
|
|||
if item is None:
|
||||
continue
|
||||
item.setData(colors, ASSET_UNDERLINE_COLORS_ROLE)
|
||||
self._item_ids_with_color.add(asset_id)
|
||||
|
||||
def _clear_items(self):
|
||||
root_item = self.invisibleRootItem()
|
||||
root_item.removeRows(0, root_item.rowCount())
|
||||
self._items_by_asset_id = {}
|
||||
self._items_with_color_by_id = {}
|
||||
self._item_ids_with_color = set()
|
||||
|
||||
def _on_docs_fetched(self):
|
||||
# Make sure refreshing did not change
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import Qt
|
|||
from Qt import QtWidgets, QtGui, QtCore
|
||||
|
||||
from avalon.lib import HeroVersionType
|
||||
from openpype.style import get_objected_colors
|
||||
from .models import TreeModel
|
||||
from . import lib
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ from openpype.api import (
|
|||
Logger
|
||||
)
|
||||
from openpype.lib import filter_profiles
|
||||
from openpype.style import get_objected_colors
|
||||
from openpype.resources import get_image_path
|
||||
|
||||
|
||||
def center_window(window):
|
||||
|
|
@ -28,6 +30,18 @@ def center_window(window):
|
|||
window.move(geo.topLeft())
|
||||
|
||||
|
||||
def set_style_property(widget, property_name, property_value):
|
||||
"""Set widget's property that may affect style.
|
||||
|
||||
If current property value is different then style of widget is polished.
|
||||
"""
|
||||
cur_value = widget.property(property_name)
|
||||
if cur_value == property_value:
|
||||
return
|
||||
widget.setProperty(property_name, property_value)
|
||||
widget.style().polish(widget)
|
||||
|
||||
|
||||
def paint_image_with_color(image, color):
|
||||
"""Redraw image with single color using it's alpha.
|
||||
|
||||
|
|
@ -670,3 +684,19 @@ class WrappedCallbackItem:
|
|||
|
||||
finally:
|
||||
self._done = True
|
||||
|
||||
|
||||
def get_warning_pixmap(color=None):
|
||||
"""Warning icon as QPixmap.
|
||||
|
||||
Args:
|
||||
color(QtGui.QColor): Color that will be used to paint warning icon.
|
||||
"""
|
||||
src_image_path = get_image_path("warning.png")
|
||||
src_image = QtGui.QImage(src_image_path)
|
||||
if color is None:
|
||||
colors = get_objected_colors()
|
||||
color_value = colors["delete-btn-bg"]
|
||||
color = color_value.get_qcolor()
|
||||
|
||||
return paint_image_with_color(src_image, color)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ import logging
|
|||
|
||||
import Qt
|
||||
from Qt import QtCore, QtGui
|
||||
from avalon.vendor import qtawesome
|
||||
from avalon import style, io
|
||||
from . import lib
|
||||
from .constants import (
|
||||
PROJECT_IS_ACTIVE_ROLE,
|
||||
PROJECT_NAME_ROLE,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import sys
|
||||
import os
|
||||
import re
|
||||
import copy
|
||||
import getpass
|
||||
import shutil
|
||||
|
|
@ -21,14 +22,13 @@ from openpype.tools.utils.tasks_widget import TasksWidget
|
|||
from openpype.tools.utils.delegates import PrettyTimeDelegate
|
||||
from openpype.lib import (
|
||||
Anatomy,
|
||||
get_workdir,
|
||||
get_workfile_doc,
|
||||
create_workfile_doc,
|
||||
save_workfile_data_to_doc,
|
||||
get_workfile_template_key,
|
||||
create_workdir_extra_folders
|
||||
create_workdir_extra_folders,
|
||||
get_system_general_anatomy_data
|
||||
)
|
||||
|
||||
from .model import FilesModel
|
||||
from .view import FilesView
|
||||
|
||||
|
|
@ -38,6 +38,185 @@ module = sys.modules[__name__]
|
|||
module.window = None
|
||||
|
||||
|
||||
def build_workfile_data(session):
|
||||
"""Get the data required for workfile formatting from avalon `session`"""
|
||||
|
||||
# Set work file data for template formatting
|
||||
asset_name = session["AVALON_ASSET"]
|
||||
task_name = session["AVALON_TASK"]
|
||||
project_doc = io.find_one(
|
||||
{"type": "project"},
|
||||
{
|
||||
"name": True,
|
||||
"data.code": True,
|
||||
"config.tasks": True,
|
||||
}
|
||||
)
|
||||
|
||||
asset_doc = io.find_one(
|
||||
{
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
},
|
||||
{
|
||||
"data.tasks": True,
|
||||
"data.parents": True
|
||||
}
|
||||
)
|
||||
|
||||
task_type = asset_doc["data"]["tasks"].get(task_name, {}).get("type")
|
||||
|
||||
project_task_types = project_doc["config"]["tasks"]
|
||||
task_short = project_task_types.get(task_type, {}).get("short_name")
|
||||
|
||||
asset_parents = asset_doc["data"]["parents"]
|
||||
parent_name = project_doc["name"]
|
||||
if asset_parents:
|
||||
parent_name = asset_parents[-1]
|
||||
|
||||
data = {
|
||||
"project": {
|
||||
"name": project_doc["name"],
|
||||
"code": project_doc["data"].get("code")
|
||||
},
|
||||
"asset": asset_name,
|
||||
"task": {
|
||||
"name": task_name,
|
||||
"type": task_type,
|
||||
"short": task_short,
|
||||
},
|
||||
"parent": parent_name,
|
||||
"version": 1,
|
||||
"user": getpass.getuser(),
|
||||
"comment": "",
|
||||
"ext": None
|
||||
}
|
||||
|
||||
# add system general settings anatomy data
|
||||
system_general_data = get_system_general_anatomy_data()
|
||||
data.update(system_general_data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class CommentMatcher(object):
|
||||
"""Use anatomy and work file data to parse comments from filenames"""
|
||||
def __init__(self, anatomy, template_key, data):
|
||||
|
||||
self.fname_regex = None
|
||||
|
||||
template = anatomy.templates[template_key]["file"]
|
||||
if "{comment}" not in template:
|
||||
# Don't look for comment if template doesn't allow it
|
||||
return
|
||||
|
||||
# Create a regex group for extensions
|
||||
extensions = api.registered_host().file_extensions()
|
||||
any_extension = "(?:{})".format(
|
||||
"|".join(re.escape(ext[1:]) for ext in extensions)
|
||||
)
|
||||
|
||||
# Use placeholders that will never be in the filename
|
||||
temp_data = copy.deepcopy(data)
|
||||
temp_data["comment"] = "<<comment>>"
|
||||
temp_data["version"] = "<<version>>"
|
||||
temp_data["ext"] = "<<ext>>"
|
||||
|
||||
formatted = anatomy.format(temp_data)
|
||||
fname_pattern = formatted[template_key]["file"]
|
||||
fname_pattern = re.escape(fname_pattern)
|
||||
|
||||
# Replace comment and version with something we can match with regex
|
||||
replacements = {
|
||||
"<<comment>>": "(.+)",
|
||||
"<<version>>": "[0-9]+",
|
||||
"<<ext>>": any_extension,
|
||||
}
|
||||
for src, dest in replacements.items():
|
||||
fname_pattern = fname_pattern.replace(re.escape(src), dest)
|
||||
|
||||
# Match from beginning to end of string to be safe
|
||||
fname_pattern = "^{}$".format(fname_pattern)
|
||||
|
||||
self.fname_regex = re.compile(fname_pattern)
|
||||
|
||||
def parse_comment(self, filepath):
|
||||
"""Parse the {comment} part from a filename"""
|
||||
if not self.fname_regex:
|
||||
return
|
||||
|
||||
fname = os.path.basename(filepath)
|
||||
match = self.fname_regex.match(fname)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
||||
|
||||
class SubversionLineEdit(QtWidgets.QWidget):
|
||||
"""QLineEdit with QPushButton for drop down selection of list of strings"""
|
||||
def __init__(self, parent=None):
|
||||
super(SubversionLineEdit, self).__init__(parent=parent)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(3)
|
||||
|
||||
self._input = PlaceholderLineEdit()
|
||||
self._button = QtWidgets.QPushButton("")
|
||||
self._button.setFixedWidth(18)
|
||||
self._menu = QtWidgets.QMenu(self)
|
||||
self._button.setMenu(self._menu)
|
||||
|
||||
layout.addWidget(self._input)
|
||||
layout.addWidget(self._button)
|
||||
|
||||
@property
|
||||
def input(self):
|
||||
return self._input
|
||||
|
||||
def set_values(self, values):
|
||||
self._update(values)
|
||||
|
||||
def _on_button_clicked(self):
|
||||
self._menu.exec_()
|
||||
|
||||
def _on_action_clicked(self, action):
|
||||
self._input.setText(action.text())
|
||||
|
||||
def _update(self, values):
|
||||
"""Create optional predefined subset names
|
||||
|
||||
Args:
|
||||
default_names(list): all predefined names
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
menu = self._menu
|
||||
button = self._button
|
||||
|
||||
state = any(values)
|
||||
button.setEnabled(state)
|
||||
if state is False:
|
||||
return
|
||||
|
||||
# Include an empty string
|
||||
values = [""] + sorted(values)
|
||||
|
||||
# Get and destroy the action group
|
||||
group = button.findChild(QtWidgets.QActionGroup)
|
||||
if group:
|
||||
group.deleteLater()
|
||||
|
||||
# Build new action group
|
||||
group = QtWidgets.QActionGroup(button)
|
||||
for name in values:
|
||||
action = group.addAction(name)
|
||||
menu.addAction(action)
|
||||
|
||||
group.triggered.connect(self._on_action_clicked)
|
||||
|
||||
|
||||
class NameWindow(QtWidgets.QDialog):
|
||||
"""Name Window to define a unique filename inside a root folder
|
||||
|
||||
|
|
@ -59,56 +238,7 @@ class NameWindow(QtWidgets.QDialog):
|
|||
# Fallback to active session
|
||||
session = api.Session
|
||||
|
||||
# Set work file data for template formatting
|
||||
asset_name = session["AVALON_ASSET"]
|
||||
task_name = session["AVALON_TASK"]
|
||||
project_doc = io.find_one(
|
||||
{"type": "project"},
|
||||
{
|
||||
"name": True,
|
||||
"data.code": True,
|
||||
"config.tasks": True,
|
||||
}
|
||||
)
|
||||
|
||||
asset_doc = io.find_one(
|
||||
{
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
},
|
||||
{
|
||||
"data.tasks": True,
|
||||
"data.parents": True
|
||||
}
|
||||
)
|
||||
|
||||
task_type = asset_doc["data"]["tasks"].get(task_name, {}).get("type")
|
||||
|
||||
project_task_types = project_doc["config"]["tasks"]
|
||||
task_short = project_task_types.get(task_type, {}).get("short_name")
|
||||
|
||||
asset_parents = asset_doc["data"]["parents"]
|
||||
parent_name = project_doc["name"]
|
||||
if asset_parents:
|
||||
parent_name = asset_parents[-1]
|
||||
|
||||
self.data = {
|
||||
"project": {
|
||||
"name": project_doc["name"],
|
||||
"code": project_doc["data"].get("code")
|
||||
},
|
||||
"asset": asset_name,
|
||||
"task": {
|
||||
"name": task_name,
|
||||
"type": task_type,
|
||||
"short": task_short,
|
||||
},
|
||||
"parent": parent_name,
|
||||
"version": 1,
|
||||
"user": getpass.getuser(),
|
||||
"comment": "",
|
||||
"ext": None
|
||||
}
|
||||
self.data = build_workfile_data(session)
|
||||
|
||||
# Store project anatomy
|
||||
self.anatomy = anatomy
|
||||
|
|
@ -151,8 +281,8 @@ class NameWindow(QtWidgets.QDialog):
|
|||
preview_label = QtWidgets.QLabel("Preview filename", inputs_widget)
|
||||
|
||||
# Subversion input
|
||||
subversion_input = PlaceholderLineEdit(inputs_widget)
|
||||
subversion_input.setPlaceholderText("Will be part of filename.")
|
||||
subversion = SubversionLineEdit(inputs_widget)
|
||||
subversion.input.setPlaceholderText("Will be part of filename.")
|
||||
|
||||
# Extensions combobox
|
||||
ext_combo = QtWidgets.QComboBox(inputs_widget)
|
||||
|
|
@ -173,9 +303,27 @@ class NameWindow(QtWidgets.QDialog):
|
|||
|
||||
# Add subversion only if template contains `{comment}`
|
||||
if "{comment}" in self.template:
|
||||
inputs_layout.addRow("Subversion:", subversion_input)
|
||||
inputs_layout.addRow("Subversion:", subversion)
|
||||
|
||||
# Detect whether a {comment} is in the current filename - if so,
|
||||
# preserve it by default and set it in the comment/subversion field
|
||||
current_filepath = self.host.current_file()
|
||||
if current_filepath:
|
||||
# We match the current filename against the current session
|
||||
# instead of the session where the user is saving to.
|
||||
current_data = build_workfile_data(api.Session)
|
||||
matcher = CommentMatcher(anatomy, template_key, current_data)
|
||||
comment = matcher.parse_comment(current_filepath)
|
||||
if comment:
|
||||
log.info("Detected subversion comment: {}".format(comment))
|
||||
self.data["comment"] = comment
|
||||
subversion.input.setText(comment)
|
||||
|
||||
existing_comments = self.get_existing_comments()
|
||||
subversion.set_values(existing_comments)
|
||||
|
||||
else:
|
||||
subversion_input.setVisible(False)
|
||||
subversion.setVisible(False)
|
||||
inputs_layout.addRow("Extension:", ext_combo)
|
||||
inputs_layout.addRow("Preview:", preview_label)
|
||||
|
||||
|
|
@ -190,7 +338,7 @@ class NameWindow(QtWidgets.QDialog):
|
|||
self.on_version_checkbox_changed
|
||||
)
|
||||
|
||||
subversion_input.textChanged.connect(self.on_comment_changed)
|
||||
subversion.input.textChanged.connect(self.on_comment_changed)
|
||||
ext_combo.currentIndexChanged.connect(self.on_extension_changed)
|
||||
|
||||
btn_ok.pressed.connect(self.on_ok_pressed)
|
||||
|
|
@ -201,7 +349,7 @@ class NameWindow(QtWidgets.QDialog):
|
|||
|
||||
# Force default focus to comment, some hosts didn't automatically
|
||||
# apply focus to this line edit (e.g. Houdini)
|
||||
subversion_input.setFocus()
|
||||
subversion.input.setFocus()
|
||||
|
||||
# Store widgets
|
||||
self.btn_ok = btn_ok
|
||||
|
|
@ -212,12 +360,32 @@ class NameWindow(QtWidgets.QDialog):
|
|||
self.last_version_check = last_version_check
|
||||
|
||||
self.preview_label = preview_label
|
||||
self.subversion_input = subversion_input
|
||||
self.subversion = subversion
|
||||
self.ext_combo = ext_combo
|
||||
self._ext_delegate = ext_delegate
|
||||
|
||||
self.refresh()
|
||||
|
||||
def get_existing_comments(self):
|
||||
|
||||
matcher = CommentMatcher(self.anatomy, self.template_key, self.data)
|
||||
host_extensions = set(self.host.file_extensions())
|
||||
comments = set()
|
||||
if os.path.isdir(self.root):
|
||||
for fname in os.listdir(self.root):
|
||||
if not os.path.isfile(os.path.join(self.root, fname)):
|
||||
continue
|
||||
|
||||
ext = os.path.splitext(fname)[-1]
|
||||
if ext not in host_extensions:
|
||||
continue
|
||||
|
||||
comment = matcher.parse_comment(fname)
|
||||
if comment:
|
||||
comments.add(comment)
|
||||
|
||||
return list(comments)
|
||||
|
||||
def on_version_spinbox_changed(self, value):
|
||||
self.data["version"] = value
|
||||
self.refresh()
|
||||
|
|
@ -558,9 +726,9 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
self.file_opened.emit()
|
||||
|
||||
def save_changes_prompt(self):
|
||||
self._messagebox = messagebox = QtWidgets.QMessageBox()
|
||||
|
||||
messagebox.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||
self._messagebox = messagebox = QtWidgets.QMessageBox(parent=self)
|
||||
messagebox.setWindowFlags(messagebox.windowFlags() |
|
||||
QtCore.Qt.FramelessWindowHint)
|
||||
messagebox.setIcon(messagebox.Warning)
|
||||
messagebox.setWindowTitle("Unsaved Changes!")
|
||||
messagebox.setText(
|
||||
|
|
@ -571,10 +739,6 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
messagebox.Yes | messagebox.No | messagebox.Cancel
|
||||
)
|
||||
|
||||
# Parenting the QMessageBox to the Widget seems to crash
|
||||
# so we skip parenting and explicitly apply the stylesheet.
|
||||
messagebox.setStyle(self.style())
|
||||
|
||||
result = messagebox.exec_()
|
||||
if result == messagebox.Yes:
|
||||
return True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue