diff --git a/openpype/tools/publisher/widgets/assets_widget.py b/openpype/tools/publisher/widgets/assets_widget.py
index 46fdcc6526..7a77c9e898 100644
--- a/openpype/tools/publisher/widgets/assets_widget.py
+++ b/openpype/tools/publisher/widgets/assets_widget.py
@@ -13,13 +13,13 @@ from openpype.tools.utils.assets_widget import (
)
-class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
+class CreateWidgetAssetsWidget(SingleSelectAssetsWidget):
current_context_required = QtCore.Signal()
header_height_changed = QtCore.Signal(int)
def __init__(self, controller, parent):
self._controller = controller
- super(CreateDialogAssetsWidget, self).__init__(None, parent)
+ super(CreateWidgetAssetsWidget, self).__init__(None, parent)
self.set_refresh_btn_visibility(False)
self.set_current_asset_btn_visibility(False)
@@ -42,11 +42,11 @@ class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
self.header_height_changed.emit(height)
def resizeEvent(self, event):
- super(CreateDialogAssetsWidget, self).resizeEvent(event)
+ super(CreateWidgetAssetsWidget, self).resizeEvent(event)
self._check_header_height()
def showEvent(self, event):
- super(CreateDialogAssetsWidget, self).showEvent(event)
+ super(CreateWidgetAssetsWidget, self).showEvent(event)
self._check_header_height()
def _on_current_asset_click(self):
@@ -63,7 +63,7 @@ class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
self.select_asset(self._last_selection)
def _select_indexes(self, *args, **kwargs):
- super(CreateDialogAssetsWidget, self)._select_indexes(*args, **kwargs)
+ super(CreateWidgetAssetsWidget, self)._select_indexes(*args, **kwargs)
if self._enabled:
return
self._last_selection = self.get_selected_asset_id()
diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py
new file mode 100644
index 0000000000..a0b3db0409
--- /dev/null
+++ b/openpype/tools/publisher/widgets/create_widget.py
@@ -0,0 +1,986 @@
+import sys
+import re
+import traceback
+import copy
+
+import qtawesome
+try:
+ import commonmark
+except Exception:
+ commonmark = None
+from Qt import QtWidgets, QtCore, QtGui
+
+from openpype.client import get_asset_by_name, get_subsets
+from openpype.pipeline.create import (
+ CreatorError,
+ SUBSET_NAME_ALLOWED_SYMBOLS,
+ TaskNotSetError,
+)
+from openpype.tools.utils import (
+ ErrorMessageBox,
+ MessageOverlayObject,
+ ClickableFrame,
+)
+
+from .widgets import IconValuePixmapLabel
+from .assets_widget import CreateWidgetAssetsWidget
+from .tasks_widget import CreateWidgetTasksWidget
+from .precreate_widget import PreCreateWidget
+from ..constants import (
+ VARIANT_TOOLTIP,
+ CREATOR_IDENTIFIER_ROLE,
+ FAMILY_ROLE
+)
+
+SEPARATORS = ("---separator---", "---")
+
+
+class VariantInputsWidget(QtWidgets.QWidget):
+ resized = QtCore.Signal()
+
+ def resizeEvent(self, event):
+ super(VariantInputsWidget, self).resizeEvent(event)
+ self.resized.emit()
+
+
+class CreateErrorMessageBox(ErrorMessageBox):
+ def __init__(
+ self,
+ creator_label,
+ subset_name,
+ asset_name,
+ exc_msg,
+ formatted_traceback,
+ parent
+ ):
+ self._creator_label = creator_label
+ self._subset_name = subset_name
+ self._asset_name = asset_name
+ self._exc_msg = exc_msg
+ self._formatted_traceback = formatted_traceback
+ super(CreateErrorMessageBox, self).__init__("Creation failed", parent)
+
+ def _create_top_widget(self, parent_widget):
+ label_widget = QtWidgets.QLabel(parent_widget)
+ label_widget.setText(
+ "Failed to create"
+ )
+ return label_widget
+
+ def _get_report_data(self):
+ report_message = (
+ "{creator}: Failed to create Subset: \"{subset}\""
+ " in Asset: \"{asset}\""
+ "\n\nError: {message}"
+ ).format(
+ creator=self._creator_label,
+ subset=self._subset_name,
+ asset=self._asset_name,
+ message=self._exc_msg,
+ )
+ if self._formatted_traceback:
+ report_message += "\n\n{}".format(self._formatted_traceback)
+ return [report_message]
+
+ def _create_content(self, content_layout):
+ item_name_template = (
+ "Creator: {}
"
+ "Subset: {}
"
+ "Asset: {}
"
+ )
+ exc_msg_template = "{}"
+
+ line = self._create_line()
+ content_layout.addWidget(line)
+
+ item_name_widget = QtWidgets.QLabel(self)
+ item_name_widget.setText(
+ item_name_template.format(
+ self._creator_label, self._subset_name, self._asset_name
+ )
+ )
+ content_layout.addWidget(item_name_widget)
+
+ message_label_widget = QtWidgets.QLabel(self)
+ message_label_widget.setText(
+ exc_msg_template.format(self.convert_text_for_html(self._exc_msg))
+ )
+ content_layout.addWidget(message_label_widget)
+
+ if self._formatted_traceback:
+ line_widget = self._create_line()
+ tb_widget = self._create_traceback_widget(
+ self._formatted_traceback
+ )
+ content_layout.addWidget(line_widget)
+ content_layout.addWidget(tb_widget)
+
+
+# TODO add creator identifier/label to details
+class CreatorShortDescWidget(QtWidgets.QWidget):
+ height_changed = QtCore.Signal(int)
+
+ def __init__(self, parent=None):
+ super(CreatorShortDescWidget, self).__init__(parent=parent)
+
+ # --- Short description widget ---
+ icon_widget = IconValuePixmapLabel(None, self)
+ icon_widget.setObjectName("FamilyIconLabel")
+
+ # --- Short description inputs ---
+ short_desc_input_widget = QtWidgets.QWidget(self)
+
+ family_label = QtWidgets.QLabel(short_desc_input_widget)
+ family_label.setAlignment(
+ QtCore.Qt.AlignBottom | QtCore.Qt.AlignLeft
+ )
+
+ description_label = QtWidgets.QLabel(short_desc_input_widget)
+ description_label.setAlignment(
+ QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft
+ )
+
+ short_desc_input_layout = QtWidgets.QVBoxLayout(
+ short_desc_input_widget
+ )
+ short_desc_input_layout.setSpacing(0)
+ short_desc_input_layout.addWidget(family_label)
+ short_desc_input_layout.addWidget(description_label)
+ # --------------------------------
+
+ layout = QtWidgets.QHBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.addWidget(icon_widget, 0)
+ layout.addWidget(short_desc_input_widget, 1)
+ # --------------------------------
+
+ self._icon_widget = icon_widget
+ self._family_label = family_label
+ self._description_label = description_label
+
+ self._last_height = None
+
+ def _check_height_change(self):
+ height = self.height()
+ if height != self._last_height:
+ self._last_height = height
+ self.height_changed.emit(height)
+
+ def showEvent(self, event):
+ super(CreatorShortDescWidget, self).showEvent(event)
+ self._check_height_change()
+
+ def resizeEvent(self, event):
+ super(CreatorShortDescWidget, self).resizeEvent(event)
+ self._check_height_change()
+
+ def set_plugin(self, plugin=None):
+ if not plugin:
+ self._icon_widget.set_icon_def(None)
+ self._family_label.setText("")
+ self._description_label.setText("")
+ return
+
+ plugin_icon = plugin.get_icon()
+ description = plugin.get_description() or ""
+
+ self._icon_widget.set_icon_def(plugin_icon)
+ self._family_label.setText("{}".format(plugin.family))
+ self._family_label.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
+ self._description_label.setText(description)
+
+
+class HelpButton(ClickableFrame):
+ resized = QtCore.Signal(int)
+ question_mark_icon_name = "fa.question"
+ help_icon_name = "fa.question-circle"
+ hide_icon_name = "fa.angle-left"
+
+ def __init__(self, *args, **kwargs):
+ super(HelpButton, self).__init__(*args, **kwargs)
+ self.setObjectName("CreateDialogHelpButton")
+
+ question_mark_label = QtWidgets.QLabel(self)
+ help_widget = QtWidgets.QWidget(self)
+
+ help_question = QtWidgets.QLabel(help_widget)
+ help_label = QtWidgets.QLabel("Help", help_widget)
+ hide_icon = QtWidgets.QLabel(help_widget)
+
+ help_layout = QtWidgets.QHBoxLayout(help_widget)
+ help_layout.setContentsMargins(0, 0, 5, 0)
+ help_layout.addWidget(help_question, 0)
+ help_layout.addWidget(help_label, 0)
+ help_layout.addStretch(1)
+ help_layout.addWidget(hide_icon, 0)
+
+ layout = QtWidgets.QHBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setSpacing(0)
+ layout.addWidget(question_mark_label, 0)
+ layout.addWidget(help_widget, 1)
+
+ help_widget.setVisible(False)
+
+ self._question_mark_label = question_mark_label
+ self._help_widget = help_widget
+ self._help_question = help_question
+ self._hide_icon = hide_icon
+
+ self._expanded = None
+ self.set_expanded()
+
+ def set_expanded(self, expanded=None):
+ if self._expanded is expanded:
+ if expanded is not None:
+ return
+ expanded = False
+ self._expanded = expanded
+ self._help_widget.setVisible(expanded)
+ self._update_content()
+
+ def _update_content(self):
+ width = self.get_icon_width()
+ if self._expanded:
+ question_mark_pix = QtGui.QPixmap(width, width)
+ question_mark_pix.fill(QtCore.Qt.transparent)
+
+ else:
+ question_mark_icon = qtawesome.icon(
+ self.question_mark_icon_name, color=QtCore.Qt.white
+ )
+ question_mark_pix = question_mark_icon.pixmap(width, width)
+
+ hide_icon = qtawesome.icon(
+ self.hide_icon_name, color=QtCore.Qt.white
+ )
+ help_question_icon = qtawesome.icon(
+ self.help_icon_name, color=QtCore.Qt.white
+ )
+ self._question_mark_label.setPixmap(question_mark_pix)
+ self._question_mark_label.setMaximumWidth(width)
+ self._hide_icon.setPixmap(hide_icon.pixmap(width, width))
+ self._help_question.setPixmap(help_question_icon.pixmap(width, width))
+
+ def get_icon_width(self):
+ metrics = self.fontMetrics()
+ return metrics.height()
+
+ def set_pos_and_size(self, pos_x, pos_y, width, height):
+ update_icon = self.height() != height
+ self.move(pos_x, pos_y)
+ self.resize(width, height)
+
+ if update_icon:
+ self._update_content()
+ self.updateGeometry()
+
+ def showEvent(self, event):
+ super(HelpButton, self).showEvent(event)
+ self.resized.emit(self.height())
+
+ def resizeEvent(self, event):
+ super(HelpButton, self).resizeEvent(event)
+ self.resized.emit(self.height())
+
+
+class CreateWidget(QtWidgets.QWidget):
+ def __init__(self, controller, parent=None):
+ super(CreateWidget, self).__init__(parent)
+
+ self.setWindowTitle("Create new instance")
+
+ self.controller = controller
+
+ self._asset_name = self.dbcon.Session.get("AVALON_ASSET")
+ self._task_name = self.dbcon.Session.get("AVALON_TASK")
+
+ self._asset_doc = None
+ self._subset_names = None
+ self._selected_creator = None
+
+ self._prereq_available = False
+
+ self._message_dialog = None
+
+ name_pattern = "^[{}]*$".format(SUBSET_NAME_ALLOWED_SYMBOLS)
+ self._name_pattern = name_pattern
+ self._compiled_name_pattern = re.compile(name_pattern)
+
+ overlay_object = MessageOverlayObject(self)
+
+ context_widget = QtWidgets.QWidget(self)
+
+ assets_widget = CreateWidgetAssetsWidget(controller, context_widget)
+ tasks_widget = CreateWidgetTasksWidget(controller, context_widget)
+
+ context_layout = QtWidgets.QVBoxLayout(context_widget)
+ context_layout.setContentsMargins(0, 0, 0, 0)
+ context_layout.setSpacing(0)
+ context_layout.addWidget(assets_widget, 2)
+ context_layout.addWidget(tasks_widget, 1)
+
+ # --- Creators view ---
+ creators_header_widget = QtWidgets.QWidget(self)
+ header_label_widget = QtWidgets.QLabel(
+ "Choose family:", creators_header_widget
+ )
+ creators_header_layout = QtWidgets.QHBoxLayout(creators_header_widget)
+ creators_header_layout.setContentsMargins(0, 0, 0, 0)
+ creators_header_layout.addWidget(header_label_widget, 1)
+
+ creators_view = QtWidgets.QListView(self)
+ creators_model = QtGui.QStandardItemModel()
+ creators_sort_model = QtCore.QSortFilterProxyModel()
+ creators_sort_model.setSourceModel(creators_model)
+ creators_view.setModel(creators_sort_model)
+
+ variant_widget = VariantInputsWidget(self)
+
+ variant_input = QtWidgets.QLineEdit(variant_widget)
+ variant_input.setObjectName("VariantInput")
+ variant_input.setToolTip(VARIANT_TOOLTIP)
+
+ variant_hints_btn = QtWidgets.QToolButton(variant_widget)
+ variant_hints_btn.setArrowType(QtCore.Qt.DownArrow)
+ variant_hints_btn.setIconSize(QtCore.QSize(12, 12))
+
+ variant_hints_menu = QtWidgets.QMenu(variant_widget)
+ variant_hints_group = QtWidgets.QActionGroup(variant_hints_menu)
+
+ variant_layout = QtWidgets.QHBoxLayout(variant_widget)
+ variant_layout.setContentsMargins(0, 0, 0, 0)
+ variant_layout.setSpacing(0)
+ variant_layout.addWidget(variant_input, 1)
+ variant_layout.addWidget(variant_hints_btn, 0, QtCore.Qt.AlignVCenter)
+
+ subset_name_input = QtWidgets.QLineEdit(self)
+ subset_name_input.setEnabled(False)
+
+ form_layout = QtWidgets.QFormLayout()
+ form_layout.addRow("Variant:", variant_widget)
+ form_layout.addRow("Subset:", subset_name_input)
+
+ mid_widget = QtWidgets.QWidget(self)
+ mid_layout = QtWidgets.QVBoxLayout(mid_widget)
+ mid_layout.setContentsMargins(0, 0, 0, 0)
+ mid_layout.addWidget(creators_header_widget, 0)
+ mid_layout.addWidget(creators_view, 1)
+ mid_layout.addLayout(form_layout, 0)
+ # ------------
+
+ # --- Creator short info and attr defs ---
+ creator_attrs_widget = QtWidgets.QWidget(self)
+
+ creator_short_desc_widget = CreatorShortDescWidget(
+ creator_attrs_widget
+ )
+
+ attr_separator_widget = QtWidgets.QWidget(self)
+ attr_separator_widget.setObjectName("Separator")
+ attr_separator_widget.setMinimumHeight(1)
+ attr_separator_widget.setMaximumHeight(1)
+
+ # Precreate attributes widget
+ pre_create_widget = PreCreateWidget(creator_attrs_widget)
+
+ # Create button
+ create_btn_wrapper = QtWidgets.QWidget(creator_attrs_widget)
+ create_btn = QtWidgets.QPushButton("Create", create_btn_wrapper)
+ create_btn.setEnabled(False)
+
+ create_btn_wrap_layout = QtWidgets.QHBoxLayout(create_btn_wrapper)
+ create_btn_wrap_layout.setContentsMargins(0, 0, 0, 0)
+ create_btn_wrap_layout.addStretch(1)
+ create_btn_wrap_layout.addWidget(create_btn, 0)
+
+ creator_attrs_layout = QtWidgets.QVBoxLayout(creator_attrs_widget)
+ creator_attrs_layout.setContentsMargins(0, 0, 0, 0)
+ creator_attrs_layout.addWidget(creator_short_desc_widget, 0)
+ creator_attrs_layout.addWidget(attr_separator_widget, 0)
+ creator_attrs_layout.addWidget(pre_create_widget, 1)
+ creator_attrs_layout.addWidget(create_btn_wrapper, 0)
+ # -------------------------------------
+
+ # --- Detailed information about creator ---
+ # Detailed description of creator
+ detail_description_widget = QtWidgets.QWidget(self)
+
+ detail_placoholder_widget = QtWidgets.QWidget(
+ detail_description_widget
+ )
+ detail_placoholder_widget.setAttribute(
+ QtCore.Qt.WA_TranslucentBackground
+ )
+
+ detail_description_input = QtWidgets.QTextEdit(
+ detail_description_widget
+ )
+ detail_description_input.setObjectName("CreatorDetailedDescription")
+ detail_description_input.setTextInteractionFlags(
+ QtCore.Qt.TextBrowserInteraction
+ )
+
+ detail_description_layout = QtWidgets.QVBoxLayout(
+ detail_description_widget
+ )
+ detail_description_layout.setContentsMargins(0, 0, 0, 0)
+ detail_description_layout.setSpacing(0)
+ detail_description_layout.addWidget(detail_placoholder_widget, 0)
+ detail_description_layout.addWidget(detail_description_input, 1)
+
+ detail_description_widget.setVisible(False)
+
+ # -------------------------------------------
+ splitter_widget = QtWidgets.QSplitter(self)
+ splitter_widget.addWidget(context_widget)
+ splitter_widget.addWidget(mid_widget)
+ splitter_widget.addWidget(creator_attrs_widget)
+ splitter_widget.addWidget(detail_description_widget)
+ splitter_widget.setStretchFactor(0, 1)
+ splitter_widget.setStretchFactor(1, 1)
+ splitter_widget.setStretchFactor(2, 1)
+ splitter_widget.setStretchFactor(3, 1)
+
+ layout = QtWidgets.QHBoxLayout(self)
+ layout.addWidget(splitter_widget, 1)
+
+ prereq_timer = QtCore.QTimer()
+ prereq_timer.setInterval(50)
+ prereq_timer.setSingleShot(True)
+
+ prereq_timer.timeout.connect(self._invalidate_prereq)
+
+ assets_widget.header_height_changed.connect(
+ self._on_asset_filter_height_change
+ )
+
+ create_btn.clicked.connect(self._on_create)
+ variant_widget.resized.connect(self._on_variant_widget_resize)
+ variant_input.returnPressed.connect(self._on_create)
+ variant_input.textChanged.connect(self._on_variant_change)
+ creators_view.selectionModel().currentChanged.connect(
+ self._on_creator_item_change
+ )
+ variant_hints_btn.clicked.connect(self._on_variant_btn_click)
+ variant_hints_menu.triggered.connect(self._on_variant_action)
+ assets_widget.selection_changed.connect(self._on_asset_change)
+ assets_widget.current_context_required.connect(
+ self._on_current_session_context_request
+ )
+ tasks_widget.task_changed.connect(self._on_task_change)
+ creator_short_desc_widget.height_changed.connect(
+ self._on_description_height_change
+ )
+
+ controller.add_plugins_refresh_callback(self._on_plugins_refresh)
+
+ self._overlay_object = overlay_object
+
+ self._splitter_widget = splitter_widget
+
+ self._context_widget = context_widget
+ self._assets_widget = assets_widget
+ self._tasks_widget = tasks_widget
+
+ self.subset_name_input = subset_name_input
+
+ self.variant_input = variant_input
+ self.variant_hints_btn = variant_hints_btn
+ self.variant_hints_menu = variant_hints_menu
+ self.variant_hints_group = variant_hints_group
+
+ self._creators_header_widget = creators_header_widget
+ self._creators_model = creators_model
+ self._creators_sort_model = creators_sort_model
+ self._creators_view = creators_view
+ self._create_btn = create_btn
+
+ self._creator_short_desc_widget = creator_short_desc_widget
+ self._pre_create_widget = pre_create_widget
+ self._attr_separator_widget = attr_separator_widget
+
+ self._detail_placoholder_widget = detail_placoholder_widget
+ self._detail_description_widget = detail_description_widget
+ self._detail_description_input = detail_description_input
+
+ self._prereq_timer = prereq_timer
+ self._first_show = True
+
+ def _emit_message(self, message):
+ self._overlay_object.add_message(message)
+
+ def _context_change_is_enabled(self):
+ return self._context_widget.isEnabled()
+
+ def _get_asset_name(self):
+ asset_name = None
+ if self._context_change_is_enabled():
+ asset_name = self._assets_widget.get_selected_asset_name()
+
+ if asset_name is None:
+ asset_name = self._asset_name
+ return asset_name
+
+ def _get_task_name(self):
+ task_name = None
+ if self._context_change_is_enabled():
+ # Don't use selection of task if asset is not set
+ asset_name = self._assets_widget.get_selected_asset_name()
+ if asset_name:
+ task_name = self._tasks_widget.get_selected_task_name()
+
+ if not task_name:
+ task_name = self._task_name
+ return task_name
+
+ @property
+ def dbcon(self):
+ return self.controller.dbcon
+
+ def _set_context_enabled(self, enabled):
+ self._assets_widget.set_enabled(enabled)
+ self._tasks_widget.set_enabled(enabled)
+ check_prereq = self._context_widget.isEnabled() != enabled
+ self._context_widget.setEnabled(enabled)
+ if check_prereq:
+ self._invalidate_prereq()
+
+ def refresh(self):
+ # Get context before refresh to keep selection of asset and
+ # task widgets
+ asset_name = self._get_asset_name()
+ task_name = self._get_task_name()
+
+ self._prereq_available = False
+
+ # Disable context widget so refresh of asset will use context asset
+ # name
+ self._set_context_enabled(False)
+
+ self._assets_widget.refresh()
+
+ # Refresh data before update of creators
+ self._refresh_asset()
+ # Then refresh creators which may trigger callbacks using refreshed
+ # data
+ self._refresh_creators()
+
+ self._assets_widget.set_current_asset_name(self._asset_name)
+ self._assets_widget.select_asset_by_name(asset_name)
+ self._tasks_widget.set_asset_name(asset_name)
+ self._tasks_widget.select_task_name(task_name)
+
+ self._invalidate_prereq_deffered()
+
+ def _invalidate_prereq_deffered(self):
+ self._prereq_timer.start()
+
+ def _on_asset_filter_height_change(self, height):
+ self._creators_header_widget.setMinimumHeight(height)
+ self._creators_header_widget.setMaximumHeight(height)
+
+ def _invalidate_prereq(self):
+ prereq_available = True
+ creator_btn_tooltips = []
+
+ available_creators = self._creators_model.rowCount() > 0
+ if available_creators != self._creators_view.isEnabled():
+ self._creators_view.setEnabled(available_creators)
+
+ if not available_creators:
+ prereq_available = False
+ creator_btn_tooltips.append("Creator is not selected")
+
+ if self._context_change_is_enabled() and self._asset_doc is None:
+ # QUESTION how to handle invalid asset?
+ prereq_available = False
+ creator_btn_tooltips.append("Context is not selected")
+
+ if prereq_available != self._prereq_available:
+ self._prereq_available = prereq_available
+
+ self._create_btn.setEnabled(prereq_available)
+
+ self.variant_input.setEnabled(prereq_available)
+ self.variant_hints_btn.setEnabled(prereq_available)
+
+ tooltip = ""
+ if creator_btn_tooltips:
+ tooltip = "\n".join(creator_btn_tooltips)
+ self._create_btn.setToolTip(tooltip)
+
+ self._on_variant_change()
+
+ def _refresh_asset(self):
+ asset_name = self._get_asset_name()
+
+ # Skip if asset did not change
+ if self._asset_doc and self._asset_doc["name"] == asset_name:
+ return
+
+ # Make sure `_asset_doc` and `_subset_names` variables are reset
+ self._asset_doc = None
+ self._subset_names = None
+ if asset_name is None:
+ return
+
+ project_name = self.dbcon.active_project()
+ asset_doc = get_asset_by_name(project_name, asset_name)
+ self._asset_doc = asset_doc
+
+ if asset_doc:
+ asset_id = asset_doc["_id"]
+ subset_docs = get_subsets(
+ project_name, asset_ids=[asset_id], fields=["name"]
+ )
+ self._subset_names = {
+ subset_doc["name"]
+ for subset_doc in subset_docs
+ }
+
+ if not asset_doc:
+ self.subset_name_input.setText("< Asset is not set >")
+
+ def _refresh_creators(self):
+ # Refresh creators and add their families to list
+ existing_items = {}
+ old_creators = set()
+ for row in range(self._creators_model.rowCount()):
+ item = self._creators_model.item(row, 0)
+ identifier = item.data(CREATOR_IDENTIFIER_ROLE)
+ existing_items[identifier] = item
+ old_creators.add(identifier)
+
+ # Add new families
+ new_creators = set()
+ for identifier, creator in self.controller.manual_creators.items():
+ # TODO add details about creator
+ new_creators.add(identifier)
+ if identifier in existing_items:
+ item = existing_items[identifier]
+ else:
+ item = QtGui.QStandardItem()
+ item.setFlags(
+ QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
+ )
+ self._creators_model.appendRow(item)
+
+ label = creator.label or identifier
+ item.setData(label, QtCore.Qt.DisplayRole)
+ item.setData(identifier, CREATOR_IDENTIFIER_ROLE)
+ item.setData(creator.family, FAMILY_ROLE)
+
+ # Remove families that are no more available
+ for identifier in (old_creators - new_creators):
+ item = existing_items[identifier]
+ self._creators_model.takeRow(item.row())
+
+ if self._creators_model.rowCount() < 1:
+ return
+
+ self._creators_sort_model.sort(0)
+ # Make sure there is a selection
+ indexes = self._creators_view.selectedIndexes()
+ if not indexes:
+ index = self._creators_sort_model.index(0, 0)
+ self._creators_view.setCurrentIndex(index)
+ else:
+ index = indexes[0]
+
+ identifier = index.data(CREATOR_IDENTIFIER_ROLE)
+
+ self._set_creator_by_identifier(identifier)
+
+ def _on_plugins_refresh(self):
+ # Trigger refresh only if is visible
+ self.refresh()
+
+ def _on_asset_change(self):
+ self._refresh_asset()
+
+ asset_name = self._assets_widget.get_selected_asset_name()
+ self._tasks_widget.set_asset_name(asset_name)
+ if self._context_change_is_enabled():
+ self._invalidate_prereq_deffered()
+
+ def _on_task_change(self):
+ if self._context_change_is_enabled():
+ self._invalidate_prereq_deffered()
+
+ def _on_current_session_context_request(self):
+ self._assets_widget.set_current_session_asset()
+ if self._task_name:
+ self._tasks_widget.select_task_name(self._task_name)
+
+ def _on_description_height_change(self):
+ # Use separator's 'y' position as height
+ height = self._attr_separator_widget.y()
+ self._detail_placoholder_widget.setMinimumHeight(height)
+ self._detail_placoholder_widget.setMaximumHeight(height)
+
+ def _on_creator_item_change(self, new_index, _old_index):
+ identifier = None
+ if new_index.isValid():
+ identifier = new_index.data(CREATOR_IDENTIFIER_ROLE)
+ self._set_creator_by_identifier(identifier)
+
+ def _set_creator_detailed_text(self, creator):
+ if not creator:
+ self._detail_description_input.setPlainText("")
+ return
+ detailed_description = creator.get_detail_description() or ""
+ if commonmark:
+ html = commonmark.commonmark(detailed_description)
+ self._detail_description_input.setHtml(html)
+ else:
+ self._detail_description_input.setMarkdown(detailed_description)
+
+ def _set_creator_by_identifier(self, identifier):
+ creator = self.controller.manual_creators.get(identifier)
+ self._set_creator(creator)
+
+ def _set_creator(self, creator):
+ self._creator_short_desc_widget.set_plugin(creator)
+ self._set_creator_detailed_text(creator)
+ self._pre_create_widget.set_plugin(creator)
+
+ self._selected_creator = creator
+
+ if not creator:
+ self._set_context_enabled(False)
+ return
+
+ if (
+ creator.create_allow_context_change
+ != self._context_change_is_enabled()
+ ):
+ self._set_context_enabled(creator.create_allow_context_change)
+ self._refresh_asset()
+
+ default_variants = creator.get_default_variants()
+ if not default_variants:
+ default_variants = ["Main"]
+
+ default_variant = creator.get_default_variant()
+ if not default_variant:
+ default_variant = default_variants[0]
+
+ for action in tuple(self.variant_hints_menu.actions()):
+ self.variant_hints_menu.removeAction(action)
+ action.deleteLater()
+
+ for variant in default_variants:
+ if variant in SEPARATORS:
+ self.variant_hints_menu.addSeparator()
+ elif variant:
+ self.variant_hints_menu.addAction(variant)
+
+ variant_text = default_variant or "Main"
+ # Make sure subset name is updated to new plugin
+ if variant_text == self.variant_input.text():
+ self._on_variant_change()
+ else:
+ self.variant_input.setText(variant_text)
+
+ def _on_variant_widget_resize(self):
+ self.variant_hints_btn.setFixedHeight(self.variant_input.height())
+
+ def _on_variant_btn_click(self):
+ pos = self.variant_hints_btn.rect().bottomLeft()
+ point = self.variant_hints_btn.mapToGlobal(pos)
+ self.variant_hints_menu.popup(point)
+
+ def _on_variant_action(self, action):
+ value = action.text()
+ if self.variant_input.text() != value:
+ self.variant_input.setText(value)
+
+ def _on_variant_change(self, variant_value=None):
+ if not self._prereq_available:
+ return
+
+ # This should probably never happen?
+ if not self._selected_creator:
+ if self.subset_name_input.text():
+ self.subset_name_input.setText("")
+ return
+
+ if variant_value is None:
+ variant_value = self.variant_input.text()
+
+ if not self._compiled_name_pattern.match(variant_value):
+ self._create_btn.setEnabled(False)
+ self._set_variant_state_property("invalid")
+ self.subset_name_input.setText("< Invalid variant >")
+ return
+
+ if not self._context_change_is_enabled():
+ self._create_btn.setEnabled(True)
+ self._set_variant_state_property("")
+ self.subset_name_input.setText("< Valid variant >")
+ return
+
+ project_name = self.controller.project_name
+ task_name = self._get_task_name()
+
+ asset_doc = copy.deepcopy(self._asset_doc)
+ # Calculate subset name with Creator plugin
+ try:
+ subset_name = self._selected_creator.get_subset_name(
+ variant_value, task_name, asset_doc, project_name
+ )
+ except TaskNotSetError:
+ self._create_btn.setEnabled(False)
+ self._set_variant_state_property("invalid")
+ self.subset_name_input.setText("< Missing task >")
+ return
+
+ self.subset_name_input.setText(subset_name)
+
+ self._create_btn.setEnabled(True)
+ self._validate_subset_name(subset_name, variant_value)
+
+ def _validate_subset_name(self, subset_name, variant_value):
+ # Get all subsets of the current asset
+ if self._subset_names:
+ existing_subset_names = set(self._subset_names)
+ else:
+ existing_subset_names = set()
+ existing_subset_names_low = set(
+ _name.lower()
+ for _name in existing_subset_names
+ )
+
+ # Replace
+ compare_regex = re.compile(re.sub(
+ variant_value, "(.+)", subset_name, flags=re.IGNORECASE
+ ))
+ variant_hints = set()
+ if variant_value:
+ for _name in existing_subset_names:
+ _result = compare_regex.search(_name)
+ if _result:
+ variant_hints |= set(_result.groups())
+
+ # Remove previous hints from menu
+ for action in tuple(self.variant_hints_group.actions()):
+ self.variant_hints_group.removeAction(action)
+ self.variant_hints_menu.removeAction(action)
+ action.deleteLater()
+
+ # Add separator if there are hints and menu already has actions
+ if variant_hints and self.variant_hints_menu.actions():
+ self.variant_hints_menu.addSeparator()
+
+ # Add hints to actions
+ for variant_hint in variant_hints:
+ action = self.variant_hints_menu.addAction(variant_hint)
+ self.variant_hints_group.addAction(action)
+
+ # Indicate subset existence
+ if not variant_value:
+ property_value = "empty"
+
+ elif subset_name.lower() in existing_subset_names_low:
+ # validate existence of subset name with lowered text
+ # - "renderMain" vs. "rendermain" mean same path item for
+ # windows
+ property_value = "exists"
+ else:
+ property_value = "new"
+
+ self._set_variant_state_property(property_value)
+
+ variant_is_valid = variant_value.strip() != ""
+ if variant_is_valid != self._create_btn.isEnabled():
+ self._create_btn.setEnabled(variant_is_valid)
+
+ def _set_variant_state_property(self, state):
+ current_value = self.variant_input.property("state")
+ if current_value != state:
+ self.variant_input.setProperty("state", state)
+ self.variant_input.style().polish(self.variant_input)
+
+ def _on_first_show(self):
+ width = self.width()
+ part = int(width / 7)
+ self._splitter_widget.setSizes(
+ [part * 2, part * 2, width - (part * 4)]
+ )
+
+ def showEvent(self, event):
+ super(CreateWidget, self).showEvent(event)
+ if self._first_show:
+ self._first_show = False
+ self._on_first_show()
+
+ def _on_create(self):
+ indexes = self._creators_view.selectedIndexes()
+ if not indexes or len(indexes) > 1:
+ return
+
+ if not self._create_btn.isEnabled():
+ return
+
+ index = indexes[0]
+ creator_label = index.data(QtCore.Qt.DisplayRole)
+ creator_identifier = index.data(CREATOR_IDENTIFIER_ROLE)
+ family = index.data(FAMILY_ROLE)
+ variant = self.variant_input.text()
+ # Care about subset name only if context change is enabled
+ subset_name = None
+ asset_name = None
+ task_name = None
+ if self._context_change_is_enabled():
+ subset_name = self.subset_name_input.text()
+ asset_name = self._get_asset_name()
+ task_name = self._get_task_name()
+
+ pre_create_data = self._pre_create_widget.current_value()
+ # Where to define these data?
+ # - what data show be stored?
+ instance_data = {
+ "asset": asset_name,
+ "task": task_name,
+ "variant": variant,
+ "family": family
+ }
+
+ error_msg = None
+ formatted_traceback = None
+ try:
+ self.controller.create(
+ creator_identifier,
+ subset_name,
+ instance_data,
+ pre_create_data
+ )
+
+ except CreatorError as exc:
+ error_msg = str(exc)
+
+ # Use bare except because some hosts raise their exceptions that
+ # do not inherit from python's `BaseException`
+ except:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ formatted_traceback = "".join(traceback.format_exception(
+ exc_type, exc_value, exc_traceback
+ ))
+ error_msg = str(exc_value)
+
+ if error_msg is None:
+ self._set_creator(self._selected_creator)
+ self._emit_message("Creation finished...")
+ else:
+ box = CreateErrorMessageBox(
+ creator_label,
+ subset_name,
+ asset_name,
+ error_msg,
+ formatted_traceback,
+ parent=self
+ )
+ box.show()
+ # Store dialog so is not garbage collected before is shown
+ self._message_dialog = box
diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py
index abdd98ff7c..ddc976d458 100644
--- a/openpype/tools/publisher/widgets/overview_widget.py
+++ b/openpype/tools/publisher/widgets/overview_widget.py
@@ -10,6 +10,7 @@ from .widgets import (
RemoveInstanceBtn,
ChangeViewBtn
)
+from .create_widget import CreateWidget
class CreateOverviewWidget(QtWidgets.QFrame):
@@ -20,9 +21,13 @@ class CreateOverviewWidget(QtWidgets.QFrame):
def __init__(self, controller, parent):
super(CreateOverviewWidget, self).__init__(parent)
- self._controller = controller
self._refreshing_instances = False
+ self._controller = controller
+ create_widget = CreateWidget(controller, self)
+
+ # --- Created Subsets/Instances ---
+ # Common widget for creation and overview
subset_views_widget = BorderedLabelWidget(
"Subsets to publish", self
)
@@ -39,6 +44,7 @@ class CreateOverviewWidget(QtWidgets.QFrame):
delete_btn = RemoveInstanceBtn(self)
change_view_btn = ChangeViewBtn(self)
+ # --- Overview ---
# Subset details widget
subset_attributes_wrap = BorderedLabelWidget(
"Publish options", self
@@ -73,6 +79,7 @@ class CreateOverviewWidget(QtWidgets.QFrame):
subset_content_widget = QtWidgets.QWidget(self)
subset_content_layout = QtWidgets.QHBoxLayout(subset_content_widget)
subset_content_layout.setContentsMargins(0, 0, 0, 0)
+ subset_content_layout.addWidget(create_widget, 7)
subset_content_layout.addWidget(subset_views_widget, 3)
subset_content_layout.addWidget(subset_attributes_wrap, 7)
@@ -86,6 +93,7 @@ class CreateOverviewWidget(QtWidgets.QFrame):
main_layout.setContentsMargins(marings)
main_layout.addWidget(subset_content_widget, 1)
+ # --- Calbacks for instances/subsets view ---
create_btn.clicked.connect(self._on_create_clicked)
delete_btn.clicked.connect(self._on_delete_clicked)
change_view_btn.clicked.connect(self._on_change_view_clicked)
@@ -109,18 +117,38 @@ class CreateOverviewWidget(QtWidgets.QFrame):
self._on_instance_context_change
)
+ # --- Controller callbacks ---
controller.add_publish_reset_callback(self._on_publish_reset)
controller.add_instances_refresh_callback(self._on_instances_refresh)
- self.subset_content_widget = subset_content_widget
+ self._subset_content_widget = subset_content_widget
- self.subset_view_cards = subset_view_cards
- self.subset_list_view = subset_list_view
- self.subset_views_layout = subset_views_layout
+ self._subset_view_cards = subset_view_cards
+ self._subset_list_view = subset_list_view
+ self._subset_views_layout = subset_views_layout
- self.delete_btn = delete_btn
+ self._delete_btn = delete_btn
- self.subset_attributes_widget = subset_attributes_widget
+ self._subset_attributes_widget = subset_attributes_widget
+ self._create_widget = create_widget
+ self._subset_attributes_wrap = subset_attributes_wrap
+
+ # Start in create mode
+ self._current_state = "create"
+ subset_attributes_wrap.setVisible(False)
+
+ def set_state(self, old_state, new_state):
+ if new_state == self._current_state:
+ return
+
+ self._current_state = new_state
+
+ self._create_widget.setVisible(
+ self._current_state == "create"
+ )
+ self._subset_attributes_wrap.setVisible(
+ self._current_state == "publish"
+ )
def _on_create_clicked(self):
"""Pass signal to parent widget which should care about changing state.
@@ -167,9 +195,9 @@ class CreateOverviewWidget(QtWidgets.QFrame):
instances, context_selected = self.get_selected_items()
# Disable delete button if nothing is selected
- self.delete_btn.setEnabled(len(instances) > 0)
+ self._delete_btn.setEnabled(len(instances) > 0)
- self.subset_attributes_widget.set_current_instances(
+ self._subset_attributes_widget.set_current_instances(
instances, context_selected
)
@@ -179,29 +207,29 @@ class CreateOverviewWidget(QtWidgets.QFrame):
self.active_changed.emit()
def _on_instance_context_change(self):
- current_idx = self.subset_views_layout.currentIndex()
- for idx in range(self.subset_views_layout.count()):
+ current_idx = self._subset_views_layout.currentIndex()
+ for idx in range(self._subset_views_layout.count()):
if idx == current_idx:
continue
- widget = self.subset_views_layout.widget(idx)
+ widget = self._subset_views_layout.widget(idx)
if widget.refreshed:
widget.set_refreshed(False)
- current_widget = self.subset_views_layout.widget(current_idx)
+ current_widget = self._subset_views_layout.widget(current_idx)
current_widget.refresh_instance_states()
self.instance_context_changed.emit()
def get_selected_items(self):
- view = self.subset_views_layout.currentWidget()
+ view = self._subset_views_layout.currentWidget()
return view.get_selected_items()
def _change_view_type(self):
- idx = self.subset_views_layout.currentIndex()
- new_idx = (idx + 1) % self.subset_views_layout.count()
- self.subset_views_layout.setCurrentIndex(new_idx)
+ idx = self._subset_views_layout.currentIndex()
+ new_idx = (idx + 1) % self._subset_views_layout.count()
+ self._subset_views_layout.setCurrentIndex(new_idx)
- new_view = self.subset_views_layout.currentWidget()
+ new_view = self._subset_views_layout.currentWidget()
if not new_view.refreshed:
new_view.refresh()
new_view.set_refreshed(True)
@@ -216,11 +244,11 @@ class CreateOverviewWidget(QtWidgets.QFrame):
self._refreshing_instances = True
- for idx in range(self.subset_views_layout.count()):
- widget = self.subset_views_layout.widget(idx)
+ for idx in range(self._subset_views_layout.count()):
+ widget = self._subset_views_layout.widget(idx)
widget.set_refreshed(False)
- view = self.subset_views_layout.currentWidget()
+ view = self._subset_views_layout.currentWidget()
view.refresh()
view.set_refreshed(True)
@@ -232,7 +260,7 @@ class CreateOverviewWidget(QtWidgets.QFrame):
def _on_publish_reset(self):
"""Context in controller has been refreshed."""
- self.subset_content_widget.setEnabled(self._controller.host_is_valid)
+ self._subset_content_widget.setEnabled(self._controller.host_is_valid)
def _on_instances_refresh(self):
"""Controller refreshed instances."""
@@ -242,5 +270,5 @@ class CreateOverviewWidget(QtWidgets.QFrame):
# Give a change to process Resize Request
QtWidgets.QApplication.processEvents()
# Trigger update geometry of
- widget = self.subset_views_layout.currentWidget()
+ widget = self._subset_views_layout.currentWidget()
widget.updateGeometry()
diff --git a/openpype/tools/publisher/widgets/tasks_widget.py b/openpype/tools/publisher/widgets/tasks_widget.py
index aa239f6334..f31fffb9ea 100644
--- a/openpype/tools/publisher/widgets/tasks_widget.py
+++ b/openpype/tools/publisher/widgets/tasks_widget.py
@@ -141,10 +141,10 @@ class TasksModel(QtGui.QStandardItemModel):
return super(TasksModel, self).headerData(section, orientation, role)
-class CreateDialogTasksWidget(TasksWidget):
+class CreateWidgetTasksWidget(TasksWidget):
def __init__(self, controller, parent):
self._controller = controller
- super(CreateDialogTasksWidget, self).__init__(None, parent)
+ super(CreateWidgetTasksWidget, self).__init__(None, parent)
self._enabled = None
@@ -164,7 +164,7 @@ class CreateDialogTasksWidget(TasksWidget):
self.task_changed.emit()
def select_task_name(self, task_name):
- super(CreateDialogTasksWidget, self).select_task_name(task_name)
+ super(CreateWidgetTasksWidget, self).select_task_name(task_name)
if not self._enabled:
current = self.get_selected_task_name()
if current: