Merge branch 'develop' of github.com:pypeclub/OpenPype into chore/OP-2414_Move-harmony-to-openpype

This commit is contained in:
Petr Kalis 2022-02-11 12:24:28 +01:00
commit 28a745d54d
71 changed files with 2628 additions and 1165 deletions

View file

@ -15,8 +15,12 @@ from .constants import (
from .actions import ApplicationAction
from Qt import QtCore, QtGui
from avalon.vendor import qtawesome
from avalon import style, api
from openpype.lib import ApplicationManager, JSONSettingRegistry
from avalon import api
from openpype.lib import JSONSettingRegistry
from openpype.lib.applications import (
CUSTOM_LAUNCH_APP_GROUPS,
ApplicationManager
)
log = logging.getLogger(__name__)
@ -72,6 +76,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),
@ -313,7 +320,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"],

View file

@ -6,6 +6,7 @@ from avalon.vendor import qtawesome
from .delegates import ActionDelegate
from . import lib
from .actions import ApplicationAction
from .models import ActionModel
from openpype.tools.flickcharm import FlickCharm
from .constants import (
@ -239,10 +240,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

View file

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

View file

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

View file

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

View file

@ -180,7 +180,16 @@ class LocalApplicationsWidgets(QtWidgets.QWidget):
self.content_layout.removeItem(item)
self.widgets_by_group_name.clear()
app_items = {}
for key, entity in self.system_settings_entity["applications"].items():
if key != "additional_apps":
app_items[key] = entity
continue
for _key, _entity in entity.items():
app_items[_key] = _entity
for key, entity in app_items.items():
# Filter not enabled app groups
if not entity["enabled"].value:
continue

View file

@ -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,
@ -88,6 +93,20 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
restart_required_trigger = 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 +117,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 +206,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
@ -308,21 +380,17 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
pass
def set_path(self, path):
self.breadcrumbs_widget.set_path(path)
self.breadcrumbs_bar.set_path(path)
def _add_developer_ui(self, footer_layout):
modify_defaults_widget = QtWidgets.QWidget()
modify_defaults_checkbox = QtWidgets.QCheckBox(modify_defaults_widget)
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 +429,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,13 +489,7 @@ 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.set_state(CategoryState.Working)
@ -444,6 +507,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
widget.deleteLater()
dialog = None
self._updating_root = True
source_version = ""
try:
self._create_root_entity()
@ -459,6 +524,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 +568,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:
@ -510,6 +597,36 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
else:
self._on_reset_success()
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 +638,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 +682,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 +698,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 +730,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 +760,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 +778,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 +794,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 +827,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 +843,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 +898,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()

View file

@ -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",
)

View file

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

View file

@ -4,7 +4,11 @@ from .categories import (
SystemWidget,
ProjectWidget
)
from .widgets import ShadowWidget, RestartDialog
from .widgets import (
ShadowWidget,
RestartDialog,
SettingsTabWidget
)
from openpype import style
from openpype.lib import is_admin_password_required
@ -34,7 +38,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)
@ -65,6 +69,10 @@ class MainWidget(QtWidgets.QWidget):
)
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
@ -100,6 +108,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:

View file

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

View file

@ -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",
)

View file

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

View file

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

View file

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

View file

@ -21,14 +21,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
@ -110,6 +109,10 @@ class NameWindow(QtWidgets.QDialog):
"ext": None
}
# add system general settings anatomy data
system_general_data = get_system_general_anatomy_data()
self.data.update(system_general_data)
# Store project anatomy
self.anatomy = anatomy
self.template = anatomy.templates[template_key]["file"]