diff --git a/openpype/tools/assetcreator/__init__.py b/openpype/tools/assetcreator/__init__.py deleted file mode 100644 index 3b88ebe9848..00000000000 --- a/openpype/tools/assetcreator/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ - -from .app import ( - show, - cli -) - -__all__ = [ - "show", - "cli", -] diff --git a/openpype/tools/assetcreator/__main__.py b/openpype/tools/assetcreator/__main__.py deleted file mode 100644 index d77bc585c5c..00000000000 --- a/openpype/tools/assetcreator/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -from . import cli - -if __name__ == '__main__': - import sys - sys.exit(cli(sys.argv[1:])) diff --git a/openpype/tools/assetcreator/app.py b/openpype/tools/assetcreator/app.py deleted file mode 100644 index 60ef31e859b..00000000000 --- a/openpype/tools/assetcreator/app.py +++ /dev/null @@ -1,654 +0,0 @@ -import os -import sys -from subprocess import Popen - -import ftrack_api -from Qt import QtWidgets, QtCore -from openpype import style -from openpype.api import get_current_project_settings -from openpype.lib.avalon_context import update_current_task -from openpype.tools.utils.lib import qt_app_context -from avalon import io, api, schema -from . import widget, model - -module = sys.modules[__name__] -module.window = None - - -class Window(QtWidgets.QDialog): - """Asset creator interface - - """ - - def __init__(self, parent=None, context=None): - super(Window, self).__init__(parent) - self.context = context - project_name = io.active_project() - self.setWindowTitle("Asset creator ({0})".format(project_name)) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - - # Validators - self.valid_parent = False - - self.session = None - - # assets widget - assets_widget = QtWidgets.QWidget() - assets_widget.setContentsMargins(0, 0, 0, 0) - assets_layout = QtWidgets.QVBoxLayout(assets_widget) - assets = widget.AssetWidget() - assets.view.setSelectionMode(assets.view.ExtendedSelection) - assets_layout.addWidget(assets) - - # Outlink - label_outlink = QtWidgets.QLabel("Outlink:") - input_outlink = QtWidgets.QLineEdit() - input_outlink.setReadOnly(True) - input_outlink.setStyleSheet("background-color: #333333;") - checkbox_outlink = QtWidgets.QCheckBox("Use outlink") - # Parent - label_parent = QtWidgets.QLabel("*Parent:") - input_parent = QtWidgets.QLineEdit() - input_parent.setReadOnly(True) - input_parent.setStyleSheet("background-color: #333333;") - - # Name - label_name = QtWidgets.QLabel("*Name:") - input_name = QtWidgets.QLineEdit() - input_name.setPlaceholderText("") - - # Asset Build - label_assetbuild = QtWidgets.QLabel("Asset Build:") - combo_assetbuilt = QtWidgets.QComboBox() - - # Task template - label_task_template = QtWidgets.QLabel("Task template:") - combo_task_template = QtWidgets.QComboBox() - - # Info widget - info_widget = QtWidgets.QWidget() - info_widget.setContentsMargins(10, 10, 10, 10) - info_layout = QtWidgets.QVBoxLayout(info_widget) - - # Inputs widget - inputs_widget = QtWidgets.QWidget() - inputs_widget.setContentsMargins(0, 0, 0, 0) - - inputs_layout = QtWidgets.QFormLayout(inputs_widget) - inputs_layout.addRow(label_outlink, input_outlink) - inputs_layout.addRow(None, checkbox_outlink) - inputs_layout.addRow(label_parent, input_parent) - inputs_layout.addRow(label_name, input_name) - inputs_layout.addRow(label_assetbuild, combo_assetbuilt) - inputs_layout.addRow(label_task_template, combo_task_template) - - # Add button - btns_widget = QtWidgets.QWidget() - btns_widget.setContentsMargins(0, 0, 0, 0) - btn_layout = QtWidgets.QHBoxLayout(btns_widget) - btn_create_asset = QtWidgets.QPushButton("Create asset") - btn_create_asset.setToolTip( - "Creates all necessary components for asset" - ) - checkbox_app = None - if self.context is not None: - checkbox_app = QtWidgets.QCheckBox("Open {}".format( - self.context.capitalize()) - ) - btn_layout.addWidget(checkbox_app) - btn_layout.addWidget(btn_create_asset) - - task_view = QtWidgets.QTreeView() - task_view.setIndentation(0) - task_model = model.TasksModel() - task_view.setModel(task_model) - - info_layout.addWidget(inputs_widget) - info_layout.addWidget(task_view) - info_layout.addWidget(btns_widget) - - # Body - body = QtWidgets.QSplitter() - body.setContentsMargins(0, 0, 0, 0) - body.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - body.setOrientation(QtCore.Qt.Horizontal) - body.addWidget(assets_widget) - body.addWidget(info_widget) - body.setStretchFactor(0, 100) - body.setStretchFactor(1, 150) - - # statusbar - message = QtWidgets.QLabel() - message.setFixedHeight(20) - - statusbar = QtWidgets.QWidget() - layout = QtWidgets.QHBoxLayout(statusbar) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(message) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(body) - layout.addWidget(statusbar) - - self.data = { - "label": { - "message": message, - }, - "view": { - "tasks": task_view - }, - "model": { - "assets": assets, - "tasks": task_model - }, - "inputs": { - "outlink": input_outlink, - "outlink_cb": checkbox_outlink, - "parent": input_parent, - "name": input_name, - "assetbuild": combo_assetbuilt, - "tasktemplate": combo_task_template, - "open_app": checkbox_app - }, - "buttons": { - "create_asset": btn_create_asset - } - } - - # signals - btn_create_asset.clicked.connect(self.create_asset) - assets.selection_changed.connect(self.on_asset_changed) - input_name.textChanged.connect(self.on_asset_name_change) - checkbox_outlink.toggled.connect(self.on_outlink_checkbox_change) - combo_task_template.currentTextChanged.connect( - self.on_task_template_changed - ) - if self.context is not None: - checkbox_app.toggled.connect(self.on_app_checkbox_change) - # on start - self.on_start() - - self.resize(600, 500) - - self.echo("Connected to project: {0}".format(project_name)) - - def open_app(self): - if self.context == 'maya': - Popen("maya") - else: - message = QtWidgets.QMessageBox(self) - message.setWindowTitle("App is not set") - message.setIcon(QtWidgets.QMessageBox.Critical) - message.show() - - def on_start(self): - project_name = io.Session['AVALON_PROJECT'] - project_query = 'Project where full_name is "{}"'.format(project_name) - if self.session is None: - session = ftrack_api.Session() - self.session = session - else: - session = self.session - ft_project = session.query(project_query).one() - schema_name = ft_project['project_schema']['name'] - # Load config - schemas_items = get_current_project_settings().get('ftrack', {}).get( - 'project_schemas', {} - ) - # Get info if it is silo project - self.silos = io.distinct("silo") - if self.silos and None in self.silos: - self.silos = None - - key = "default" - if schema_name in schemas_items: - key = schema_name - - self.config_data = schemas_items[key] - - # set outlink - input_outlink = self.data['inputs']['outlink'] - checkbox_outlink = self.data['inputs']['outlink_cb'] - outlink_text = io.Session.get('AVALON_ASSET', '') - checkbox_outlink.setChecked(True) - if outlink_text == '': - outlink_text = '< No context >' - checkbox_outlink.setChecked(False) - checkbox_outlink.hide() - input_outlink.setText(outlink_text) - - # load asset build types - self.load_assetbuild_types() - - # Load task templates - self.load_task_templates() - self.data["model"]["assets"].refresh() - self.on_asset_changed() - - def create_asset(self): - name_input = self.data['inputs']['name'] - name = name_input.text() - test_name = name.replace(' ', '') - error_message = None - message = QtWidgets.QMessageBox(self) - message.setWindowTitle("Some errors have occurred") - message.setIcon(QtWidgets.QMessageBox.Critical) - # TODO: show error messages on any error - if self.valid_parent is not True and test_name == '': - error_message = "Name is not set and Parent is not selected" - elif self.valid_parent is not True: - error_message = "Parent is not selected" - elif test_name == '': - error_message = "Name is not set" - - if error_message is not None: - message.setText(error_message) - message.show() - return - - test_name_exists = io.find({ - 'type': 'asset', - 'name': name - }) - existing_assets = [x for x in test_name_exists] - if len(existing_assets) > 0: - message.setText("Entered Asset name is occupied") - message.show() - return - - checkbox_app = self.data['inputs']['open_app'] - if checkbox_app is not None and checkbox_app.isChecked() is True: - task_view = self.data["view"]["tasks"] - task_model = self.data["model"]["tasks"] - try: - index = task_view.selectedIndexes()[0] - task_name = task_model.itemData(index)[0] - except Exception: - message.setText("Please select task") - message.show() - return - - # Get ftrack session - if self.session is None: - session = ftrack_api.Session() - self.session = session - else: - session = self.session - - # Get Ftrack project entity - project_name = io.Session['AVALON_PROJECT'] - project_query = 'Project where full_name is "{}"'.format(project_name) - try: - ft_project = session.query(project_query).one() - except Exception: - message.setText("Ftrack project was not found") - message.show() - return - - # Get Ftrack entity of parent - ft_parent = None - assets_model = self.data["model"]["assets"] - selected = assets_model.get_selected_assets() - parent = io.find_one({"_id": selected[0], "type": "asset"}) - asset_id = parent.get('data', {}).get('ftrackId', None) - asset_entity_type = parent.get('data', {}).get('entityType', None) - asset_query = '{} where id is "{}"' - if asset_id is not None and asset_entity_type is not None: - try: - ft_parent = session.query(asset_query.format( - asset_entity_type, asset_id) - ).one() - except Exception: - ft_parent = None - - if ft_parent is None: - ft_parent = self.get_ftrack_asset(parent, ft_project) - - if ft_parent is None: - message.setText("Parent's Ftrack entity was not found") - message.show() - return - - asset_build_combo = self.data['inputs']['assetbuild'] - asset_type_name = asset_build_combo.currentText() - asset_type_query = 'Type where name is "{}"'.format(asset_type_name) - try: - asset_type = session.query(asset_type_query).one() - except Exception: - message.setText("Selected Asset Build type does not exists") - message.show() - return - - for children in ft_parent['children']: - if children['name'] == name: - message.setText("Entered Asset name is occupied") - message.show() - return - - task_template_combo = self.data['inputs']['tasktemplate'] - task_template = task_template_combo.currentText() - tasks = [] - for template in self.config_data['task_templates']: - if template['name'] == task_template: - tasks = template['task_types'] - break - - available_task_types = [] - task_types = ft_project['project_schema']['_task_type_schema'] - for task_type in task_types['types']: - available_task_types.append(task_type['name']) - - not_possible_tasks = [] - for task in tasks: - if task not in available_task_types: - not_possible_tasks.append(task) - - if len(not_possible_tasks) != 0: - message.setText(( - "These Task types weren't found" - " in Ftrack project schema:\n{}").format( - ', '.join(not_possible_tasks)) - ) - message.show() - return - - # Create asset build - asset_build_data = { - 'name': name, - 'project_id': ft_project['id'], - 'parent_id': ft_parent['id'], - 'type': asset_type - } - - new_entity = session.create('AssetBuild', asset_build_data) - - task_data = { - 'project_id': ft_project['id'], - 'parent_id': new_entity['id'] - } - - for task in tasks: - type = session.query('Type where name is "{}"'.format(task)).one() - - task_data['type_id'] = type['id'] - task_data['name'] = task - session.create('Task', task_data) - - av_project = io.find_one({'type': 'project'}) - - hiearchy_items = [] - hiearchy_items.extend(self.get_avalon_parent(parent)) - hiearchy_items.append(parent['name']) - - hierarchy = os.path.sep.join(hiearchy_items) - new_asset_data = { - 'ftrackId': new_entity['id'], - 'entityType': new_entity.entity_type, - 'visualParent': parent['_id'], - 'tasks': tasks, - 'parents': hiearchy_items, - 'hierarchy': hierarchy - } - new_asset_info = { - 'parent': av_project['_id'], - 'name': name, - 'schema': "openpype:asset-3.0", - 'type': 'asset', - 'data': new_asset_data - } - - # Backwards compatibility (add silo from parent if is silo project) - if self.silos: - new_asset_info["silo"] = parent["silo"] - - try: - schema.validate(new_asset_info) - except Exception: - message.setText(( - 'Asset information are not valid' - ' to create asset in avalon database' - )) - message.show() - session.rollback() - return - io.insert_one(new_asset_info) - session.commit() - - outlink_cb = self.data['inputs']['outlink_cb'] - if outlink_cb.isChecked() is True: - outlink_input = self.data['inputs']['outlink'] - outlink_name = outlink_input.text() - outlink_asset = io.find_one({ - 'type': 'asset', - 'name': outlink_name - }) - outlink_ft_id = outlink_asset.get('data', {}).get('ftrackId', None) - outlink_entity_type = outlink_asset.get( - 'data', {} - ).get('entityType', None) - if outlink_ft_id is not None and outlink_entity_type is not None: - try: - outlink_entity = session.query(asset_query.format()).one() - except Exception: - outlink_entity = None - - if outlink_entity is None: - outlink_entity = self.get_ftrack_asset( - outlink_asset, ft_project - ) - - if outlink_entity is None: - message.setText("Outlink's Ftrack entity was not found") - message.show() - return - - link_data = { - 'from_id': new_entity['id'], - 'to_id': outlink_entity['id'] - } - session.create('TypedContextLink', link_data) - session.commit() - - if checkbox_app is not None and checkbox_app.isChecked() is True: - origin_asset = api.Session.get('AVALON_ASSET', None) - origin_task = api.Session.get('AVALON_TASK', None) - asset_name = name - task_view = self.data["view"]["tasks"] - task_model = self.data["model"]["tasks"] - try: - index = task_view.selectedIndexes()[0] - except Exception: - message.setText("No task is selected. App won't be launched") - message.show() - return - task_name = task_model.itemData(index)[0] - try: - update_current_task(task=task_name, asset=asset_name) - self.open_app() - - finally: - if origin_task is not None and origin_asset is not None: - update_current_task( - task=origin_task, asset=origin_asset - ) - - message.setWindowTitle("Asset Created") - message.setText("Asset Created successfully") - message.setIcon(QtWidgets.QMessageBox.Information) - message.show() - - def get_ftrack_asset(self, asset, ft_project): - parenthood = [] - parenthood.extend(self.get_avalon_parent(asset)) - parenthood.append(asset['name']) - parenthood = list(reversed(parenthood)) - output_entity = None - ft_entity = ft_project - index = len(parenthood) - 1 - while True: - name = parenthood[index] - found = False - for children in ft_entity['children']: - if children['name'] == name: - ft_entity = children - found = True - break - if found is False: - return None - if index == 0: - output_entity = ft_entity - break - index -= 1 - - return output_entity - - def get_avalon_parent(self, entity): - parent_id = entity['data']['visualParent'] - parents = [] - if parent_id is not None: - parent = io.find_one({'_id': parent_id}) - parents.extend(self.get_avalon_parent(parent)) - parents.append(parent['name']) - return parents - - def echo(self, message): - widget = self.data["label"]["message"] - widget.setText(str(message)) - - QtCore.QTimer.singleShot(5000, lambda: widget.setText("")) - - print(message) - - def load_task_templates(self): - templates = self.config_data.get('task_templates', []) - all_names = [] - for template in templates: - all_names.append(template['name']) - - tt_combobox = self.data['inputs']['tasktemplate'] - tt_combobox.clear() - tt_combobox.addItems(all_names) - - def load_assetbuild_types(self): - types = [] - schemas = self.config_data.get('schemas', []) - for _schema in schemas: - if _schema['object_type'] == 'Asset Build': - types = _schema['task_types'] - break - ab_combobox = self.data['inputs']['assetbuild'] - ab_combobox.clear() - ab_combobox.addItems(types) - - def on_app_checkbox_change(self): - task_model = self.data['model']['tasks'] - app_checkbox = self.data['inputs']['open_app'] - if app_checkbox.isChecked() is True: - task_model.selectable = True - else: - task_model.selectable = False - - def on_outlink_checkbox_change(self): - checkbox_outlink = self.data['inputs']['outlink_cb'] - outlink_input = self.data['inputs']['outlink'] - if checkbox_outlink.isChecked() is True: - outlink_text = io.Session['AVALON_ASSET'] - else: - outlink_text = '< Outlinks won\'t be set >' - - outlink_input.setText(outlink_text) - - def on_task_template_changed(self): - combobox = self.data['inputs']['tasktemplate'] - task_model = self.data['model']['tasks'] - name = combobox.currentText() - tasks = [] - for template in self.config_data['task_templates']: - if template['name'] == name: - tasks = template['task_types'] - break - task_model.set_tasks(tasks) - - def on_asset_changed(self): - """Callback on asset selection changed - - This updates the task view. - - """ - assets_model = self.data["model"]["assets"] - parent_input = self.data['inputs']['parent'] - selected = assets_model.get_selected_assets() - - self.valid_parent = False - if len(selected) > 1: - parent_input.setText('< Please select only one asset! >') - elif len(selected) == 1: - if isinstance(selected[0], io.ObjectId): - self.valid_parent = True - asset = io.find_one({"_id": selected[0], "type": "asset"}) - parent_input.setText(asset['name']) - else: - parent_input.setText('< Selected invalid parent(silo) >') - else: - parent_input.setText('< Nothing is selected >') - - self.creatability_check() - - def on_asset_name_change(self): - self.creatability_check() - - def creatability_check(self): - name_input = self.data['inputs']['name'] - name = str(name_input.text()).strip() - creatable = False - if name and self.valid_parent: - creatable = True - - self.data["buttons"]["create_asset"].setEnabled(creatable) - - - -def show(parent=None, debug=False, context=None): - """Display Loader GUI - - Arguments: - debug (bool, optional): Run loader in debug-mode, - defaults to False - - """ - - try: - module.window.close() - del module.window - except (RuntimeError, AttributeError): - pass - - if debug is True: - io.install() - - with qt_app_context(): - window = Window(parent, context) - window.setStyleSheet(style.load_stylesheet()) - window.show() - - module.window = window - - -def cli(args): - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("project") - parser.add_argument("asset") - - args = parser.parse_args(args) - project = args.project - asset = args.asset - io.install() - - api.Session["AVALON_PROJECT"] = project - if asset != '': - api.Session["AVALON_ASSET"] = asset - - show() diff --git a/openpype/tools/assetcreator/model.py b/openpype/tools/assetcreator/model.py deleted file mode 100644 index f84541ca2a9..00000000000 --- a/openpype/tools/assetcreator/model.py +++ /dev/null @@ -1,310 +0,0 @@ -import re -import logging - -from Qt import QtCore, QtWidgets -from avalon.vendor import qtawesome -from avalon import io -from avalon import style - -log = logging.getLogger(__name__) - - -class Item(dict): - """An item that can be represented in a tree view using `TreeModel`. - - The item can store data just like a regular dictionary. - - >>> data = {"name": "John", "score": 10} - >>> item = Item(data) - >>> assert item["name"] == "John" - - """ - - def __init__(self, data=None): - super(Item, self).__init__() - - self._children = list() - self._parent = None - - if data is not None: - assert isinstance(data, dict) - self.update(data) - - def childCount(self): - return len(self._children) - - def child(self, row): - - if row >= len(self._children): - log.warning("Invalid row as child: {0}".format(row)) - return - - return self._children[row] - - def children(self): - return self._children - - def parent(self): - return self._parent - - def row(self): - """ - Returns: - int: Index of this item under parent""" - if self._parent is not None: - siblings = self.parent().children() - return siblings.index(self) - - def add_child(self, child): - """Add a child to this item""" - child._parent = self - self._children.append(child) - - -class TreeModel(QtCore.QAbstractItemModel): - - Columns = list() - ItemRole = QtCore.Qt.UserRole + 1 - - def __init__(self, parent=None): - super(TreeModel, self).__init__(parent) - self._root_item = Item() - - def rowCount(self, parent): - if parent.isValid(): - item = parent.internalPointer() - else: - item = self._root_item - - return item.childCount() - - def columnCount(self, parent): - return len(self.Columns) - - def data(self, index, role): - - if not index.isValid(): - return None - - if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole: - - item = index.internalPointer() - column = index.column() - - key = self.Columns[column] - return item.get(key, None) - - if role == self.ItemRole: - return index.internalPointer() - - def setData(self, index, value, role=QtCore.Qt.EditRole): - """Change the data on the items. - - Returns: - bool: Whether the edit was successful - """ - - if index.isValid(): - if role == QtCore.Qt.EditRole: - - item = index.internalPointer() - column = index.column() - key = self.Columns[column] - item[key] = value - - # passing `list()` for PyQt5 (see PYSIDE-462) - self.dataChanged.emit(index, index, list()) - - # must return true if successful - return True - - return False - - def setColumns(self, keys): - assert isinstance(keys, (list, tuple)) - self.Columns = keys - - def headerData(self, section, orientation, role): - - if role == QtCore.Qt.DisplayRole: - if section < len(self.Columns): - return self.Columns[section] - - super(TreeModel, self).headerData(section, orientation, role) - - def flags(self, index): - flags = QtCore.Qt.ItemIsEnabled - - item = index.internalPointer() - if item.get("enabled", True): - flags |= QtCore.Qt.ItemIsSelectable - - return flags - - def parent(self, index): - - item = index.internalPointer() - parent_item = item.parent() - - # If it has no parents we return invalid - if parent_item == self._root_item or not parent_item: - return QtCore.QModelIndex() - - return self.createIndex(parent_item.row(), 0, parent_item) - - def index(self, row, column, parent): - """Return index for row/column under parent""" - - if not parent.isValid(): - parent_item = self._root_item - else: - parent_item = parent.internalPointer() - - child_item = parent_item.child(row) - if child_item: - return self.createIndex(row, column, child_item) - else: - return QtCore.QModelIndex() - - def add_child(self, item, parent=None): - if parent is None: - parent = self._root_item - - parent.add_child(item) - - def column_name(self, column): - """Return column key by index""" - - if column < len(self.Columns): - return self.Columns[column] - - def clear(self): - self.beginResetModel() - self._root_item = Item() - self.endResetModel() - - -class TasksModel(TreeModel): - """A model listing the tasks combined for a list of assets""" - - Columns = ["Tasks"] - - def __init__(self): - super(TasksModel, self).__init__() - self._num_assets = 0 - self._icons = { - "__default__": qtawesome.icon("fa.male", - color=style.colors.default), - "__no_task__": qtawesome.icon("fa.exclamation-circle", - color=style.colors.mid) - } - - self._get_task_icons() - - def _get_task_icons(self): - # Get the project configured icons from database - project = io.find_one({"type": "project"}) - tasks = project["config"].get("tasks", []) - for task in tasks: - icon_name = task.get("icon", None) - if icon_name: - icon = qtawesome.icon("fa.{}".format(icon_name), - color=style.colors.default) - self._icons[task["name"]] = icon - - def set_tasks(self, tasks): - """Set assets to track by their database id - - Arguments: - asset_ids (list): List of asset ids. - - """ - - self.clear() - - # let cleared task view if no tasks are available - if len(tasks) == 0: - return - - self.beginResetModel() - - icon = self._icons["__default__"] - for task in tasks: - item = Item({ - "Tasks": task, - "icon": icon - }) - - self.add_child(item) - - self.endResetModel() - - def flags(self, index): - return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable - - def headerData(self, section, orientation, role): - - # Override header for count column to show amount of assets - # it is listing the tasks for - if role == QtCore.Qt.DisplayRole: - if orientation == QtCore.Qt.Horizontal: - if section == 1: # count column - return "count ({0})".format(self._num_assets) - - return super(TasksModel, self).headerData(section, orientation, role) - - def data(self, index, role): - - if not index.isValid(): - return - - # Add icon to the first column - if role == QtCore.Qt.DecorationRole: - if index.column() == 0: - return index.internalPointer()["icon"] - - return super(TasksModel, self).data(index, role) - - -class DeselectableTreeView(QtWidgets.QTreeView): - """A tree view that deselects on clicking on an empty area in the view""" - - def mousePressEvent(self, event): - - index = self.indexAt(event.pos()) - if not index.isValid(): - # clear the selection - self.clearSelection() - # clear the current index - self.setCurrentIndex(QtCore.QModelIndex()) - - QtWidgets.QTreeView.mousePressEvent(self, event) - - -class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): - """Filters to the regex if any of the children matches allow parent""" - def filterAcceptsRow(self, row, parent): - - regex = self.filterRegExp() - if not regex.isEmpty(): - pattern = regex.pattern() - model = self.sourceModel() - source_index = model.index(row, self.filterKeyColumn(), parent) - if source_index.isValid(): - - # Check current index itself - key = model.data(source_index, self.filterRole()) - if re.search(pattern, key, re.IGNORECASE): - return True - - # Check children - rows = model.rowCount(source_index) - for i in range(rows): - if self.filterAcceptsRow(i, source_index): - return True - - # Otherwise filter it - return False - - return super(RecursiveSortFilterProxyModel, - self).filterAcceptsRow(row, parent) diff --git a/openpype/tools/assetcreator/widget.py b/openpype/tools/assetcreator/widget.py deleted file mode 100644 index fd0f438e68a..00000000000 --- a/openpype/tools/assetcreator/widget.py +++ /dev/null @@ -1,448 +0,0 @@ -import logging -import contextlib -import collections - -from avalon.vendor import qtawesome -from Qt import QtWidgets, QtCore, QtGui -from avalon import style, io - -from .model import ( - TreeModel, - Item, - RecursiveSortFilterProxyModel, - DeselectableTreeView -) - -log = logging.getLogger(__name__) - - -def _iter_model_rows(model, - column, - include_root=False): - """Iterate over all row indices in a model""" - indices = [QtCore.QModelIndex()] # start iteration at root - - for index in indices: - - # Add children to the iterations - child_rows = model.rowCount(index) - for child_row in range(child_rows): - child_index = model.index(child_row, column, index) - indices.append(child_index) - - if not include_root and not index.isValid(): - continue - - yield index - - -@contextlib.contextmanager -def preserve_expanded_rows(tree_view, - column=0, - role=QtCore.Qt.DisplayRole): - """Preserves expanded row in QTreeView by column's data role. - - This function is created to maintain the expand vs collapse status of - the model items. When refresh is triggered the items which are expanded - will stay expanded and vice versa. - - Arguments: - tree_view (QWidgets.QTreeView): the tree view which is - nested in the application - column (int): the column to retrieve the data from - role (int): the role which dictates what will be returned - - Returns: - None - - """ - - model = tree_view.model() - - expanded = set() - - for index in _iter_model_rows(model, - column=column, - include_root=False): - if tree_view.isExpanded(index): - value = index.data(role) - expanded.add(value) - - try: - yield - finally: - if not expanded: - return - - for index in _iter_model_rows(model, - column=column, - include_root=False): - value = index.data(role) - state = value in expanded - if state: - tree_view.expand(index) - else: - tree_view.collapse(index) - - -@contextlib.contextmanager -def preserve_selection(tree_view, - column=0, - role=QtCore.Qt.DisplayRole, - current_index=True): - """Preserves row selection in QTreeView by column's data role. - - This function is created to maintain the selection status of - the model items. When refresh is triggered the items which are expanded - will stay expanded and vice versa. - - tree_view (QWidgets.QTreeView): the tree view nested in the application - column (int): the column to retrieve the data from - role (int): the role which dictates what will be returned - - Returns: - None - - """ - - model = tree_view.model() - selection_model = tree_view.selectionModel() - flags = selection_model.Select | selection_model.Rows - - if current_index: - current_index_value = tree_view.currentIndex().data(role) - else: - current_index_value = None - - selected_rows = selection_model.selectedRows() - if not selected_rows: - yield - return - - selected = set(row.data(role) for row in selected_rows) - try: - yield - finally: - if not selected: - return - - # Go through all indices, select the ones with similar data - for index in _iter_model_rows(model, - column=column, - include_root=False): - - value = index.data(role) - state = value in selected - if state: - tree_view.scrollTo(index) # Ensure item is visible - selection_model.select(index, flags) - - if current_index_value and value == current_index_value: - tree_view.setCurrentIndex(index) - - -class AssetModel(TreeModel): - """A model listing assets in the silo in the active project. - - The assets are displayed in a treeview, they are visually parented by - a `visualParent` field in the database containing an `_id` to a parent - asset. - - """ - - Columns = ["label"] - Name = 0 - Deprecated = 2 - ObjectId = 3 - - DocumentRole = QtCore.Qt.UserRole + 2 - ObjectIdRole = QtCore.Qt.UserRole + 3 - - def __init__(self, parent=None): - super(AssetModel, self).__init__(parent=parent) - self.refresh() - - def _add_hierarchy(self, assets, parent=None, silos=None): - """Add the assets that are related to the parent as children items. - - This method does *not* query the database. These instead are queried - in a single batch upfront as an optimization to reduce database - queries. Resulting in up to 10x speed increase. - - Args: - assets (dict): All assets in the currently active silo stored - by key/value - - Returns: - None - - """ - if silos: - # WARNING: Silo item "_id" is set to silo value - # mainly because GUI issue with preserve selection and expanded row - # and because of easier hierarchy parenting (in "assets") - for silo in silos: - item = Item({ - "_id": silo, - "name": silo, - "label": silo, - "type": "silo" - }) - self.add_child(item, parent=parent) - self._add_hierarchy(assets, parent=item) - - parent_id = parent["_id"] if parent else None - current_assets = assets.get(parent_id, list()) - - for asset in current_assets: - # get label from data, otherwise use name - data = asset.get("data", {}) - label = data.get("label", asset["name"]) - tags = data.get("tags", []) - - # store for the asset for optimization - deprecated = "deprecated" in tags - - item = Item({ - "_id": asset["_id"], - "name": asset["name"], - "label": label, - "type": asset["type"], - "tags": ", ".join(tags), - "deprecated": deprecated, - "_document": asset - }) - self.add_child(item, parent=parent) - - # Add asset's children recursively if it has children - if asset["_id"] in assets: - self._add_hierarchy(assets, parent=item) - - def refresh(self): - """Refresh the data for the model.""" - - self.clear() - self.beginResetModel() - - # Get all assets in current silo sorted by name - db_assets = io.find({"type": "asset"}).sort("name", 1) - silos = db_assets.distinct("silo") or None - # if any silo is set to None then it's expected it should not be used - if silos and None in silos: - silos = None - - # Group the assets by their visual parent's id - assets_by_parent = collections.defaultdict(list) - for asset in db_assets: - parent_id = ( - asset.get("data", {}).get("visualParent") or - asset.get("silo") - ) - assets_by_parent[parent_id].append(asset) - - # Build the hierarchical tree items recursively - self._add_hierarchy( - assets_by_parent, - parent=None, - silos=silos - ) - - self.endResetModel() - - def flags(self, index): - return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable - - def data(self, index, role): - - if not index.isValid(): - return - - item = index.internalPointer() - if role == QtCore.Qt.DecorationRole: # icon - - column = index.column() - if column == self.Name: - - # Allow a custom icon and custom icon color to be defined - data = item.get("_document", {}).get("data", {}) - icon = data.get("icon", None) - if icon is None and item.get("type") == "silo": - icon = "database" - color = data.get("color", style.colors.default) - - if icon is None: - # Use default icons if no custom one is specified. - # If it has children show a full folder, otherwise - # show an open folder - has_children = self.rowCount(index) > 0 - icon = "folder" if has_children else "folder-o" - - # Make the color darker when the asset is deprecated - if item.get("deprecated", False): - color = QtGui.QColor(color).darker(250) - - try: - key = "fa.{0}".format(icon) # font-awesome key - icon = qtawesome.icon(key, color=color) - return icon - except Exception as exception: - # Log an error message instead of erroring out completely - # when the icon couldn't be created (e.g. invalid name) - log.error(exception) - - return - - if role == QtCore.Qt.ForegroundRole: # font color - if "deprecated" in item.get("tags", []): - return QtGui.QColor(style.colors.light).darker(250) - - if role == self.ObjectIdRole: - return item.get("_id", None) - - if role == self.DocumentRole: - return item.get("_document", None) - - return super(AssetModel, self).data(index, role) - - -class AssetWidget(QtWidgets.QWidget): - """A Widget to display a tree of assets with filter - - To list the assets of the active project: - >>> # widget = AssetWidget() - >>> # widget.refresh() - >>> # widget.show() - - """ - - assets_refreshed = QtCore.Signal() # on model refresh - selection_changed = QtCore.Signal() # on view selection change - current_changed = QtCore.Signal() # on view current index change - - def __init__(self, parent=None): - super(AssetWidget, self).__init__(parent=parent) - self.setContentsMargins(0, 0, 0, 0) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(4) - - # Tree View - model = AssetModel(self) - proxy = RecursiveSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - - view = DeselectableTreeView() - view.setIndentation(15) - view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - view.setHeaderHidden(True) - view.setModel(proxy) - - # Header - header = QtWidgets.QHBoxLayout() - - icon = qtawesome.icon("fa.refresh", color=style.colors.light) - refresh = QtWidgets.QPushButton(icon, "") - refresh.setToolTip("Refresh items") - - filter = QtWidgets.QLineEdit() - filter.textChanged.connect(proxy.setFilterFixedString) - filter.setPlaceholderText("Filter assets..") - - header.addWidget(filter) - header.addWidget(refresh) - - # Layout - layout.addLayout(header) - layout.addWidget(view) - - # Signals/Slots - selection = view.selectionModel() - selection.selectionChanged.connect(self.selection_changed) - selection.currentChanged.connect(self.current_changed) - refresh.clicked.connect(self.refresh) - - self.refreshButton = refresh - self.model = model - self.proxy = proxy - self.view = view - - def _refresh_model(self): - with preserve_expanded_rows( - self.view, column=0, role=self.model.ObjectIdRole - ): - with preserve_selection( - self.view, column=0, role=self.model.ObjectIdRole - ): - self.model.refresh() - - self.assets_refreshed.emit() - - def refresh(self): - self._refresh_model() - - def get_active_asset(self): - """Return the asset id the current asset.""" - current = self.view.currentIndex() - return current.data(self.model.ItemRole) - - def get_active_index(self): - return self.view.currentIndex() - - def get_selected_assets(self): - """Return the assets' ids that are selected.""" - selection = self.view.selectionModel() - rows = selection.selectedRows() - return [row.data(self.model.ObjectIdRole) for row in rows] - - def select_assets(self, assets, expand=True, key="name"): - """Select assets by name. - - Args: - assets (list): List of asset names - expand (bool): Whether to also expand to the asset in the view - - Returns: - None - - """ - # TODO: Instead of individual selection optimize for many assets - - if not isinstance(assets, (tuple, list)): - assets = [assets] - assert isinstance( - assets, (tuple, list) - ), "Assets must be list or tuple" - - # convert to list - tuple cant be modified - assets = list(assets) - - # Clear selection - selection_model = self.view.selectionModel() - selection_model.clearSelection() - - # Select - mode = selection_model.Select | selection_model.Rows - for index in iter_model_rows( - self.proxy, column=0, include_root=False - ): - # stop iteration if there are no assets to process - if not assets: - break - - value = index.data(self.model.ItemRole).get(key) - if value not in assets: - continue - - # Remove processed asset - assets.pop(assets.index(value)) - - selection_model.select(index, mode) - - if expand: - # Expand parent index - self.view.expand(self.proxy.parent(index)) - - # Set the currently active index - self.view.setCurrentIndex(index)