class SelectorWidget(QtWidgets.QWidget): container_picked = QtCore.Signal(str, dict) host_selected = QtCore.Signal(str) version_changed = QtCore.Signal(str, io.ObjectId) def __init__(self, side, parent=None): super(SelectorWidget, self).__init__(parent=parent) def icon(name): return qtawesome.icon("fa.{}".format(name), color=SIDE_COLOR[side]) body = { "tab": QtWidgets.QTabWidget(), } selector = { "host": HostSelectorWidget(), "databse": DatabaseSelectorWidget(), } body["tab"].addTab(selector["databse"], icon("cloud"), "Published") body["tab"].addTab(selector["host"], icon("home"), "In Scene") layout = QtWidgets.QHBoxLayout(self) layout.addWidget(body["tab"]) # Connect selector["host"].container_picked.connect(self.on_container_picked) selector["host"].host_selected.connect(self.on_host_selected) selector["databse"].version_changed.connect(self.on_version_changed) # Init self.selector = selector self.side = side if not has_host() or side == SIDE_B: body["tab"].setCurrentIndex(0) else: body["tab"].setCurrentIndex(1) def connect_comparer(self, comparer): self.container_picked.connect(comparer.on_container_picked) self.host_selected.connect(comparer.on_host_selected) self.version_changed.connect(comparer.on_version_changed) def on_container_picked(self, container): self.container_picked.emit(self.side, container) def on_host_selected(self): self.host_selected.emit(self.side) def on_version_changed(self, version_id): self.version_changed.emit(self.side, version_id)
class MatchOutliner(QtWidgets.QWidget): selection_changed = QtCore.Signal() def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) # look manager layout layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(10) # Looks from database title = QtWidgets.QLabel("Matches:") title.setAlignment(QtCore.Qt.AlignLeft) title.setStyleSheet("font-weight: bold; font-size: 12px") model = models.MatchModel() view = views.View() view.setModel(model) view.setSortingEnabled(False) view.setHeaderHidden(True) view.setMinimumHeight(180) view.setIndentation(10) layout.addWidget(title) layout.addWidget(view) selection_model = view.selectionModel() selection_model.selectionChanged.connect(self.selection_changed) self.view = view self.model = model self._selection_model = selection_model def clear(self): self.model.clear() def add_items(self, items): self.model.add_items(items) def clear_selection(self): flags = self._selection_model.Clear self._selection_model.select(QtCore.QModelIndex(), flags) def select_index(self, index, flags=None): flags = flags or self._selection_model.ClearAndSelect self._selection_model.select(index, flags) def get_selected_items(self): """Get current selected items from view Returns: list: list of dictionaries """ datas = [i.data(NODEROLE) for i in self.view.get_indices()] items = [d for d in datas if d is not None] # filter Nones return items
class ResolutionDelegate(QtWidgets.QStyledItemDelegate): # DEPRECATED value_changed = QtCore.Signal(list) def displayText(self, value, locale): return "{} x {}".format(*value) def createEditor(self, parent, option, index): editor = ResolutionEditor(parent) def commit_data(value): self.commitData.emit(editor) # Update model data self.value_changed.emit(value) editor.value_changed.connect(commit_data) return editor def setEditorData(self, editor, index): value = index.data(QtCore.Qt.DisplayRole) editor.set_value(value) def setModelData(self, editor, model, index): value = editor.get_value() model.setData(index, value) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect)
class NameEditDelegate(QtWidgets.QStyledItemDelegate): name_changed = QtCore.Signal() def displayText(self, value, locale): return value def createEditor(self, parent, option, index): editor = QtWidgets.QLineEdit(parent) def commit_data(): self.commitData.emit(editor) # Update model data self.name_changed.emit() # Display model data editor.editingFinished.connect(commit_data) return editor def setEditorData(self, editor, index): value = index.data(QtCore.Qt.DisplayRole) editor.setText(value) def setModelData(self, editor, model, index): name = editor.text() model.setData(index, name) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect)
class Navigation(QtWidgets.QWidget): """Navigation panel widget""" index_changed = QtCore.Signal(int) log = logging.getLogger("Navigation") def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent=parent) # self.setWindowFlag() self.setStyleSheet(style.flat_button) layout = QtWidgets.QVBoxLayout() dynamic_layout = QtWidgets.QVBoxLayout() static_layout = QtWidgets.QVBoxLayout() static_layout.addStretch() layout.addLayout(dynamic_layout) layout.addStretch() layout.addLayout(static_layout) self.dynamic_layout = dynamic_layout self.static_layout = static_layout self.setLayout(layout) self.layout = layout def add_button(self, label, order, widget=None): """Add a button to the navigation panel with the given label and order Args: label(str): Name displayed on the button order(int): Number which dictates its order in the panel widget(QtWidgets.QWidget): Instance of a widget Returns: None """ if order < 0: layout = self.static_layout else: layout = self.dynamic_layout # Check new position widget_at_item = layout.itemAt(abs(order)) if widget_at_item: self.log.warning("Found multiple items for the same order: `%i`" % order) return button = QtWidgets.QPushButton(label) if widget: button.clicked.connect(partial(widget.show)) layout.insertWidget(order, button) return button
class View(QtWidgets.QTreeView): data_changed = QtCore.Signal() def __init__(self, parent=None): super(View, self).__init__(parent=parent) # view settings self.setAlternatingRowColors(False) self.setSortingEnabled(True) self.setSelectionMode(self.SingleSelection) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) def get_indices(self): """Get the selected rows""" selection_model = self.selectionModel() return selection_model.selectedRows()
class View(QtWidgets.QTreeView): data_changed = QtCore.Signal() def __init__(self, parent=None): super(View, self).__init__(parent=parent) # view settings self.setAlternatingRowColors(False) self.setSortingEnabled(True) self.setSelectionMode(self.ExtendedSelection) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) def get_indices(self): """Get the selected rows""" selection_model = self.selectionModel() return selection_model.selectedRows() def extend_to_children(self, indices): """Extend the indices to the children indices. Top-level indices are extended to its children indices. Sub-items are kept as is. :param indices: The indices to extend. :type indices: list :return: The children indices :rtype: list """ subitems = set() for i in indices: valid_parent = i.parent().isValid() if valid_parent and i not in subitems: subitems.add(i) else: # is top level node model = i.model() rows = model.rowCount(parent=i) for row in range(rows): child = model.index(row, 0, parent=i) subitems.add(child) return list(subitems)
class Popup2(Popup): on_show = QtCore.Signal() def __init__(self, parent=None, *args, **kwargs): Popup.__init__(self, parent=parent, *args, **kwargs) layout = self.layout() # Add toggle toggle = QtWidgets.QCheckBox("Update Keys") layout.insertWidget(1, toggle) self.widgets["toggle"] = toggle layout.insertStretch(1, 1) # Update button text fix = self.widgets["show"] fix.setText("Fix")
class CollectionItem(QtWidgets.QListWidgetItem): InstanceRole = QtCore.Qt.UserRole + 2 item_clicked = QtCore.Signal() def __init__(self, data=None, parent=None): QtWidgets.QListWidgetItem.__init__(self, parent) self.setFlags(self.flags() | QtCore.Qt.ItemIsUserCheckable) self.setCheckState(QtCore.Qt.Checked) self._data = None if data: self.set_data(data) def set_data(self, data): self._data = data self.setData(self.InstanceRole, data) def get_data(self): return self._data
class ResolutionEditor(QtWidgets.QWidget): # DEPRECATED value_changed = QtCore.Signal(list) def __init__(self, parent=None): super(ResolutionEditor, self).__init__(parent) weditor = QtWidgets.QLineEdit() heditor = QtWidgets.QLineEdit() weditor.setValidator(QtGui.QIntValidator()) heditor.setValidator(QtGui.QIntValidator()) layout = QtWidgets.QHBoxLayout(self) layout.addWidget(weditor) layout.addWidget(heditor) # This is important to make those QLineEdit widgets to have # proper hight in column. layout.setContentsMargins(2, 0, 2, 0) weditor.editingFinished.connect(self.on_editingFinished) heditor.editingFinished.connect(self.on_editingFinished) self.data = { "w": weditor, "h": heditor, } def on_editingFinished(self): self.value_changed.emit(self.get_value()) def get_value(self): return (int(self.data["w"].text() or 0), int(self.data["h"].text() or 0)) def set_value(self, value): w, h = value self.data["w"].setText(str(w)) self.data["h"].setText(str(h))
class Popup2(Popup): on_show = QtCore.Signal() def __init__(self, parent=None, *args, **kwargs): Popup.__init__(self, parent=parent, *args, **kwargs) layout = self.layout() # Add toggle toggle = QtWidgets.QCheckBox("Update Keys") layout.insertWidget(1, toggle) self.widgets["toggle"] = toggle layout.insertStretch(1, 1) # Update button text fix = self.widgets["show"] fix.setText("Fix") def calculate_window_geometry(self): """Respond to status changes On creation, align window with screen bottom right. """ parent_widget = self.parent() app = QtWidgets.QApplication.instance() if parent_widget: screen = app.desktop().screenNumber(parent_widget) else: screen = app.desktop().screenNumber(app.desktop().cursor().pos()) center_point = app.desktop().screenGeometry(screen).center() frame_geo = self.frameGeometry() frame_geo.moveCenter(center_point) return frame_geo
class PopupUpdateKeys(Popup): """Popup with Update Keys checkbox (intended for Maya)""" on_clicked_state = QtCore.Signal(bool) def __init__(self, parent=None, *args, **kwargs): Popup.__init__(self, parent=parent, *args, **kwargs) layout = self.layout() # Insert toggle for Update keys toggle = QtWidgets.QCheckBox("Update Keys") layout.insertWidget(1, toggle) self.widgets["toggle"] = toggle self.on_clicked.connect(self.emit_click_with_state) layout.insertStretch(1, 1) def emit_click_with_state(self): """Emit the on_clicked_state signal with the toggled state""" checked = self.widgets["toggle"].isChecked() self.on_clicked_state.emit(checked)
class Window(QtWidgets.QWidget): project_changed = QtCore.Signal(str) log = logging.getLogger("Project Manager") def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Project Manager") self.resize(1200, 800) self.projects = {} layout = QtWidgets.QVBoxLayout() # Main control ctrl_button_w = 30 ctrl_button_h = 30 # Calculate icon size icon_size = QtCore.QSize(ctrl_button_w - 4, ctrl_button_h - 4) main_control_layout = QtWidgets.QHBoxLayout() database_label = QtWidgets.QLabel() # Main buttons - create create_button = QtWidgets.QPushButton() create_icon = qta.icon("fa.plus-square", color=style.colors.light) create_button.setIconSize(icon_size) create_button.setFixedWidth(ctrl_button_w) create_button.setFixedHeight(ctrl_button_h) create_button.setIcon(create_icon) create_button.setStyleSheet(cbstyle.flat_button) # Main buttons - refresh refresh_button = QtWidgets.QPushButton() refresh_icon = qta.icon("fa.refresh", color=style.colors.light) refresh_button.setIconSize(icon_size) refresh_button.setFixedWidth(ctrl_button_w) refresh_button.setFixedHeight(ctrl_button_h) refresh_button.setIcon(refresh_icon) refresh_button.setStyleSheet(cbstyle.flat_button) # Project switch control projects_label = QtWidgets.QLabel("Project:") projects = QtWidgets.QComboBox() projects.insertItem(0, "<None>") # Add buttons to the main control layout main_control_layout.addWidget(create_button) main_control_layout.addStretch() main_control_layout.addWidget(database_label) main_control_layout.addWidget(projects_label) main_control_layout.addWidget(projects) main_control_layout.addWidget(refresh_button) # Splitter for tabwidget and preview / details widget split_widget = QtWidgets.QSplitter() # Widgets will be stored in a StackedWidget stacked_widget = QtWidgets.QStackedWidget() # Control widgets which make the tool manager_widget = ManageProjectWidget(parent=self) # Sub manager widgets overview = OverviewWidget() manager_widget.add_widget(overview) stacked_widget.insertWidget(manager_widget.order, manager_widget) # Navigation panel widget navigation_panel = Navigation() # Add buttons to navigation panel navigation_panel.add_button(manager_widget.label, manager_widget.order) # Add widgets to the SplitWidget split_widget.addWidget(navigation_panel) split_widget.addWidget(stacked_widget) split_widget.setHandleWidth(4) split_widget.setSizes([100, 700]) layout.addLayout(main_control_layout) layout.addWidget(split_widget) self.setLayout(layout) # To connect widget store in self attribute self._navigation_panel = navigation_panel self._stacked_widget = stacked_widget self._database_label = database_label self._create_button = create_button self._projects = projects self._refresh_button = refresh_button self._overview = overview self.connect_signals() self.refresh() manager_widget.setFocus(True) def connect_signals(self): """Create connections between widgets""" self._navigation_panel.index_changed.connect( self._stacked_widget.setCurrentIndex) self.project_changed.connect(self.on_project_changed) self._refresh_button.clicked.connect(self.refresh) self._create_button.clicked.connect(self.on_create) self._projects.currentIndexChanged.connect( self.on_project_index_changed) def refresh(self): """Refresh connection to database and """ def sorter(project): """Sort based on order attribute of the plugin""" return project["name"] lib.install() self.set_database_label(lib.get_database_name()) query = list(lib.get_projects()) projects = sorted(query, key=sorter) self.projects = {p["name"]: p["_id"] for p in projects} self.populate_projects(projects) def set_database_label(self, name=None): label = "Database: {}".format(name or "<None>") self._database_label.setText(label) def populate_projects(self, projects): """Add projects to project dropdown menu""" completer = QtWidgets.QCompleter([p["name"] for p in projects]) self._projects.setCompleter(completer) for idx, project in enumerate(projects): self._projects.insertItem(idx + 1, project["name"], userData=project["_id"]) def get_project(self, as_id=False): current_index = self._projects.currentIndex() if current_index == 0: return item = self._projects.itemAt(current_index) if as_id: _id = item.userData() assert _id, "This is a bug!" return _id project_name = item.text() assert project_name, "This is a bug!" return project_name def on_project_changed(self, name): current_project = self._projects.currentText() if name == current_project: return self.refresh() idx = self._projects.findText(name) if idx == -1: raise RuntimeError("Something went wrong, can't find name `%s`" % name) self._projects.setCurrentIndex(idx) # Refresh the overview with self._overview.refresh(name) def on_project_index_changed(self): project_name = self._projects.currentText() self._overview.refresh(project_name) def on_create(self): create_widget = CreateProjectWidget(parent=self) create_widget.data_changed.connect(self.on_project_changed) create_widget.show()
class Window(QtWidgets.QWidget): UserRole = QtCore.Qt.UserRole connected = QtCore.Signal(list) disconnected = QtCore.Signal(list) def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent=parent) title = "Yeti Rig Manager 1.1.0 - [%s]" % lib.get_workfile() geometry = (800, 400) self.log = logging.getLogger("Yeti Rig Manager") self.setObjectName("yetiRigManager") self.setWindowTitle(title) self.setWindowFlags(QtCore.Qt.Window) self.setParent(parent) layout = QtWidgets.QVBoxLayout() # Control layout control_layout = QtWidgets.QHBoxLayout() force_checkbox = QtWidgets.QCheckBox("Force") force_checkbox.setChecked(True) refresh_button = QtWidgets.QPushButton() refresh_icon = qta.icon("fa.refresh", color="white") refresh_button.setIcon(refresh_icon) refresh_button.setFixedWidth(28) refresh_button.setFixedHeight(28) control_layout.addWidget(force_checkbox) control_layout.addStretch(True) control_layout.addWidget(refresh_button) view_layout = QtWidgets.QHBoxLayout() rig_view = AssetOutliner() match_view = MatchOutliner() view_layout.addWidget(rig_view) view_layout.addWidget(match_view) # Default action buttons action_button_layout = QtWidgets.QHBoxLayout() connect_button = QtWidgets.QPushButton("Connect") disconnect_button = QtWidgets.QPushButton("Disconnect") action_button_layout.addWidget(connect_button) action_button_layout.addWidget(disconnect_button) layout.addLayout(control_layout) layout.addLayout(view_layout) layout.addLayout(action_button_layout) self.setLayout(layout) self.force_checkbox = force_checkbox self.refresh_button = refresh_button self.connect_button = connect_button self.disconnect_button = disconnect_button self.rig_view = rig_view self.match_view = match_view self.resize(*geometry) self.connections() def connections(self): self.rig_view.selection_changed.connect(self.on_rig_selection_changed) self.refresh_button.clicked.connect(self.refresh) self.connect_button.clicked.connect(self.connect_container_nodes) self.disconnect_button.clicked.connect(self.disconnect_container_nodes) def on_rig_selection_changed(self): selection_model = self.rig_view.get_selection_model() indices = selection_model.selectedIndexes() if len(indices) != 1: return index = indices[0] self.match_view.model.set_linked_index(index) # The font will only update the widget gets focus self.match_view.setFocus() self.rig_view.setFocus() def refresh(self): self.rig_view.clear() self.match_view.clear() rig_items = [] other_items = [] # Separate based on loader for container in lib.get_containers(): node = lib.create_node(container) if node["loader"] == "YetiRigLoader": rig_items.append(node) else: other_items.append(node) match_items = lib.get_matches(rig_items, other_items) print(match_items) self.rig_view.add_items(rig_items) self.match_view.add_items(match_items) self.log.info("Refreshed ..") self._link_connected() def connect_container_nodes(self): force = self.force_checkbox.isChecked() rig_node = self._get_rig_node() match_node = self._get_match_node() # Get needs information connections = lib.get_connections(rig_node["representation"]) rig_members_by_id = rig_node["nodes"] input_members_by_id = match_node["nodes"] lib.connect(rig_members_by_id, input_members_by_id, connections, force) self.refresh() def disconnect_container_nodes(self): rig_node = self._get_rig_node() if not rig_node: self.log.error("Please select one rig item") match_node = self._get_match_node() if not match_node: self.log.error("Please select one match item") connections = lib.get_connections(rig_node["representation"]) rig_members_by_id = rig_node["nodes"] input_members_by_id = match_node["nodes"] lib.disconnect(rig_members_by_id, input_members_by_id, connections) self.refresh() def _get_rig_node(self): items = self.rig_view.get_selected_items() if len(items) != 1: return item = items[0] return item def _get_match_node(self): items = self.match_view.get_selected_items() if len(items) != 1: return item = items[0] return item def _link_connected(self): rig_model = self.rig_view.model rig_indexes = rig_model.get_indexes() match_model = self.match_view.model match_indexes = match_model.get_indexes() node_role = rig_model.NodeRole for rig_index in rig_indexes: if not rig_index.isValid(): continue rig_node = rig_model.data(rig_index, node_role) rig_members_by_id = rig_node["nodes"] connections = lib.get_connections(rig_node["representation"]) for match_index in match_indexes: if not match_index.isValid(): continue match_node = match_model.data(match_index, node_role) match_members_by_id = match_node["nodes"] if not lib.are_items_connected( rig_members_by_id, match_members_by_id, connections): continue self.log.info("Found connected items..") match_node.update({"linkedIndex": [rig_index]}) break def _find_rig_node_index(self, label): model = self.rig_view.model indexes = model.get_indexes() for idx in indexes: if not idx.isValid(): continue node = model.data(idx, model.NodeRole) if node["label"] == label: return idx def _find_match_node_index(self, label): model = self.match_view.model indexes = model.get_indexes() for idx in indexes: if not idx.isValid(): continue node = model.data(idx, model.NodeRole) if node["label"] == label: return idx
class AssetOutliner(QtWidgets.QWidget): refreshed = QtCore.Signal() selection_changed = QtCore.Signal() def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) layout = QtWidgets.QVBoxLayout() title = QtWidgets.QLabel("Assets") title.setAlignment(QtCore.Qt.AlignCenter) title.setStyleSheet("font-weight: bold; font-size: 12px") model = models.AssetModel() view = views.View() view.setModel(model) view.customContextMenuRequested.connect(self.right_mouse_menu) view.setSortingEnabled(False) view.setHeaderHidden(False) view.setIndentation(10) from_all_asset_btn = QtWidgets.QPushButton("Get All Assets") from_selection_btn = QtWidgets.QPushButton("Get Assets From Selection") layout.addWidget(title) layout.addWidget(from_all_asset_btn) layout.addWidget(from_selection_btn) layout.addWidget(view) # Build connections from_selection_btn.clicked.connect(self.get_selected_assets) from_all_asset_btn.clicked.connect(self.get_all_assets) selection_model = view.selectionModel() selection_model.selectionChanged.connect(self.selection_changed) self.view = view self.model = model self.setLayout(layout) self.log = logging.getLogger(__name__) def clear(self): self.model.clear() # fix looks remaining visible when no items present after "refresh" # todo: figure out why this workaround is needed. self.selection_changed.emit() def add_items(self, items): """Add new items to the outliner""" self.model.add_items(items) self.refreshed.emit() def get_selected_items(self): """Get current selected items from view Returns: list: list of dictionaries """ selection_model = self.view.selectionModel() items = [row.data(NODEROLE) for row in selection_model.selectedRows(0)] return items def get_all_assets(self): """Add all items from the current scene""" with preserve_expanded_rows(self.view): with preserve_selection(self.view): self.clear() nodes = commands.get_all_asset_nodes() items = commands.create_items_from_nodes(nodes) self.add_items(items) return len(items) > 0 def get_selected_assets(self): """Add all selected items from the current scene""" with preserve_expanded_rows(self.view): with preserve_selection(self.view): self.clear() nodes = commands.get_selected_nodes() items = commands.create_items_from_nodes(nodes) self.add_items(items) def get_nodes(self): """Find the nodes in the current scene per asset.""" items = self.get_selected_items() # Collect the asset item entries per asset assets = dict() for item in items: asset_name = item["asset"]["name"] namespaces = item.get("namespace", item["namespaces"]) nodes = commands.get_groups_from_namespaces(namespaces) assets[item.get("namespace") or asset_name] = item assets[item.get("namespace") or asset_name]["nodes"] = nodes return assets def select_asset_from_items(self): """Select nodes from listed asset""" items = self.get_nodes() nodes = [] for item in items.values(): nodes.extend(item["nodes"]) commands.select(nodes) def remove_look_from_items(self): namespaces = set() asset_ids = set() for item in self.get_selected_items(): namespace = item.get("namespace") if namespace: namespaces.add(namespace) else: namespaces.update(item["namespaces"]) asset_ids.add(str(item["asset"]["_id"])) commands.remove_look(namespaces, asset_ids) def right_mouse_menu(self, pos): """Build RMB menu for asset outliner""" active = self.view.currentIndex() # index under mouse active = active.sibling(active.row(), 0) # get first column globalpos = self.view.viewport().mapToGlobal(pos) menu = QtWidgets.QMenu(self.view) apply_action = QtWidgets.QAction(menu, text="Select nodes") apply_action.triggered.connect(self.select_asset_from_items) remove_action = QtWidgets.QAction(menu, text="Remove look") remove_action.triggered.connect(self.remove_look_from_items) if not active.isValid(): apply_action.setEnabled(False) remove_action.setEnabled(False) menu.addAction(apply_action) menu.addAction(remove_action) menu.exec_(globalpos)
class View(QtWidgets.QTreeView): data_changed = QtCore.Signal() def __init__(self, parent=None): super(View, self).__init__(parent=parent) # view settings self.setIndentation(6) self.setAlternatingRowColors(True) self.setSortingEnabled(True) self.setSelectionMode(self.ExtendedSelection) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) def get_indices(self): """Get the selected rows""" selection_model = self.selectionModel() return selection_model.selectedRows() def extend_to_children(self, indices): """Extend the indices to the children indices. Top-level indices are extended to its children indices. Sub-items are kept as is. :param indices: The indices to extend. :type indices: list :return: The children indices :rtype: list """ subitems = set() for i in indices: valid_parent = i.parent().isValid() if valid_parent and i not in subitems: subitems.add(i) else: # is top level node model = i.model() rows = model.rowCount(parent=i) for row in range(rows): child = model.index(row, 0, parent=i) subitems.add(child) return list(subitems) def show_version_dialog(self, items): """Create a dialog with the available versions for the selected file :param items: list of items to run the "set_version" for :type items: list :returns: None """ active = items[-1] # Get available versions for active representation representation_id = io.ObjectId(active["representation"]) representation = io.find_one({"_id": representation_id}) version = io.find_one({"_id": representation["parent"]}) versions = io.find({"parent": version["parent"]}, sort=[("name", 1)]) versions = list(versions) current_version = active["version"] # Get index among the listed versions index = len(versions) - 1 for i, version in enumerate(versions): if version["name"] == current_version: index = i break versions_by_label = dict() labels = [] for version in versions: label = "v{0:03d}".format(version["name"]) labels.append(label) versions_by_label[label] = version label, state = QtWidgets.QInputDialog.getItem(self, "Set version..", "Set version number " "to", labels, current=index, editable=False) if not state: return if label: version = versions_by_label[label]["name"] for item in items: api.update(item, version) # refresh model when done self.data_changed.emit()
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)
class FocusComparing(QtWidgets.QWidget): focus_enabled = QtCore.Signal(int) def __init__(self, parent=None): super(FocusComparing, self).__init__(parent=parent) widget = pindict.to_pindict({ "overallDiff": { "main": QtWidgets.QGroupBox("Compare Features"), "name": { "main": QtWidgets.QWidget(), "icon": QtWidgets.QLabel(), "label": QtWidgets.QLabel("Hierarchy"), "status": QtWidgets.QLabel("--"), }, "id": { "main": QtWidgets.QWidget(), "icon": QtWidgets.QLabel(), "label": QtWidgets.QLabel("Avalon Id"), "status": QtWidgets.QLabel("--"), }, "mesh": { "main": QtWidgets.QWidget(), "icon": QtWidgets.QLabel(), "label": QtWidgets.QLabel("Mesh"), "status": QtWidgets.QLabel("--"), }, "uv": { "main": QtWidgets.QWidget(), "icon": QtWidgets.QLabel(), "label": QtWidgets.QLabel("UV"), "status": QtWidgets.QLabel("--"), }, }, "featureMenu": { "main": QtWidgets.QWidget(), "label": QtWidgets.QLabel("Focus On"), "list": QtWidgets.QComboBox(), }, "focus": { "view": QtWidgets.QTreeView(), "model": models.FocusModel(), "pathDelegate": delegates.PathTextDelegate(), } }) with widget.pin("overallDiff") as diff: layout = QtWidgets.QVBoxLayout(diff["main"]) for key in ["name", "id", "mesh", "uv"]: with widget.pin("overallDiff." + key) as feature: lay = QtWidgets.QHBoxLayout(feature["main"]) lay.addWidget(feature["label"]) lay.addSpacing(8) lay.addWidget(feature["icon"]) lay.addSpacing(12) lay.addWidget(feature["status"], stretch=True) feature["label"].setFixedWidth(60) feature["label"].setAlignment(QtCore.Qt.AlignRight) icon = delegates.FEATURE_ICONS[key] pixmap = lib.icon(icon, models.COLOR_DARK).pixmap(16, 16) feature["icon"].setPixmap(pixmap) layout.addWidget(feature["main"]) layout.addSpacing(-16) layout.addSpacing(16) layout.setContentsMargins(0, 0, 0, 0) with widget.pin("featureMenu") as menu: layout = QtWidgets.QHBoxLayout(menu["main"]) layout.addWidget(menu["label"]) layout.addWidget(menu["list"]) layout.addStretch() layout = QtWidgets.QVBoxLayout(self) layout.addSpacing(-16) layout.addWidget(widget["overallDiff"]["main"]) layout.addSpacing(-8) layout.addWidget(widget["featureMenu"]["main"]) layout.addSpacing(-8) layout.addWidget(widget["focus"]["view"]) # Init with widget.pin("featureMenu") as menu: menu["list"].addItem(" Hierarchy", "longName") menu["list"].addItem(" Avalon Id", "avalonId") menu["list"].addItem(" Full Path", "fullPath") menu["list"].addItem(" Mesh", "points") menu["list"].addItem(" UV", "uvmap") with widget.pin("focus") as focus: focus["view"].setModel(focus["model"]) focus["view"].setItemDelegateForColumn(1, focus["pathDelegate"]) focus["view"].setHeaderHidden(True) focus["view"].setUniformRowHeights(True) focus["view"].setAlternatingRowColors(False) focus["view"].setIndentation(6) focus["view"].setStyleSheet(""" QTreeView::item{ padding: 2px 1px; border: 0px; } """) focus["view"].setSelectionMode(focus["view"].NoSelection) height = focus["view"].sizeHintForRow(0) * 2 + 4 # MagicNum focus["view"].setFixedHeight(height) focus["view"].setColumnWidth(0, 28) self.widget = widget self._focusing = False # Connect with widget.pin("featureMenu") as menu: menu["list"].currentIndexChanged.connect(self.on_feature_changed) self.focus_enabled.connect(self.on_focus_enabled) def on_focus_enabled(self, enable): if not enable: self.widget["focus"]["model"].reset_sides() self.update() # Reset self._focusing = enable def on_feature_changed(self, index=None): with self.widget.pin("featureMenu.list") as menu: feature = menu.currentData() with self.widget.pin("focus") as focus: focus["model"].set_focus(feature) def on_picked(self, side, data=None): if not self._focusing: return with self.widget.pin("focus") as focus: data = data or dict() focus["model"].set_side(side, data) # Compare self.update() self.on_feature_changed() def update(self): with self.widget.pin("focus") as focus: node_A = focus["model"].nodes[SIDE_A] node_B = focus["model"].nodes[SIDE_B] def related(this, that): return this == that or this.endswith(that) or that.endswith(this) def equal(this, that): return this == that features = [ ("name", "longName", related), ("id", "avalonId", equal), ("mesh", "points", equal), ("uv", "uvmap", equal), ] for feature, key, compare in features: with self.widget.pin("overallDiff." + feature) as widget: fet_A = node_A.get(key) fet_B = node_B.get(key) if fet_A and fet_B: if compare(fet_A, fet_B): status = "Match" color = models.COLOR_BRIGHT else: status = "Not Match" color = models.COLOR_DANGER else: status = "--" color = models.COLOR_DARK icon = delegates.FEATURE_ICONS[feature] pixmap = lib.icon(icon, color).pixmap(16, 16) widget["icon"].setPixmap(pixmap) widget["status"].setText(status)
class AssetOutliner(QtWidgets.QWidget): refreshed = QtCore.Signal() selection_changed = QtCore.Signal() def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) layout = QtWidgets.QVBoxLayout() title = QtWidgets.QLabel("Assets") title.setAlignment(QtCore.Qt.AlignCenter) title.setStyleSheet("font-weight: bold; font-size: 12px") model = models.AssetModel() view = views.View() view.setModel(model) view.customContextMenuRequested.connect(self.right_mouse_menu) view.setSortingEnabled(False) view.setHeaderHidden(True) view.setIndentation(10) from_all_asset_btn = QtWidgets.QPushButton("Get All Assets") from_selection_btn = QtWidgets.QPushButton("Get Assets From Selection") layout.addWidget(title) layout.addWidget(from_all_asset_btn) layout.addWidget(from_selection_btn) layout.addWidget(view) # Build connections from_selection_btn.clicked.connect(self.get_selected_assets) from_all_asset_btn.clicked.connect(self.get_all_assets) selection_model = view.selectionModel() selection_model.selectionChanged.connect(self.selection_changed) self.view = view self.model = model self.setLayout(layout) self.log = logging.getLogger(__name__) def clear(self): self.model.clear() # fix looks remaining visible when no items present after "refresh" # todo: figure out why this workaround is needed. self.selection_changed.emit() def add_items(self, items): """Add new items to the outliner""" self.model.add_items(items) self.refreshed.emit() def get_selected_items(self): """Get current selected items from view Returns: list: list of dictionaries """ selection_model = self.view.selectionModel() items = [ row.data(TreeModel.ItemRole) for row in selection_model.selectedRows(0) ] return items def get_all_assets(self): """Add all items from the current scene""" with lib.preserve_expanded_rows(self.view): with lib.preserve_selection(self.view): self.clear() nodes = commands.get_all_asset_nodes() items = commands.create_items_from_nodes(nodes) self.add_items(items) return len(items) > 0 def get_selected_assets(self): """Add all selected items from the current scene""" with lib.preserve_expanded_rows(self.view): with lib.preserve_selection(self.view): self.clear() nodes = commands.get_selected_nodes() items = commands.create_items_from_nodes(nodes) self.add_items(items) def get_nodes(self, selection=False): """Find the nodes in the current scene per asset.""" items = self.get_selected_items() # Collect all nodes by hash (optimization) if not selection: nodes = cmds.ls(dag=True, long=True) else: nodes = commands.get_selected_nodes() id_nodes = commands.create_asset_id_hash(nodes) # Collect the asset item entries per asset # and collect the namespaces we'd like to apply assets = dict() asset_namespaces = defaultdict(set) for item in items: asset_id = str(item["asset"]["_id"]) asset_name = item["asset"]["name"] asset_namespaces[asset_name].add(item.get("namespace")) if asset_name in assets: continue assets[asset_name] = item assets[asset_name]["nodes"] = id_nodes.get(asset_id, []) # Filter nodes to namespace (if only namespaces were selected) for asset_name in assets: namespaces = asset_namespaces[asset_name] # When None is present there should be no filtering if None in namespaces: continue # Else only namespaces are selected and *not* the top entry so # we should filter to only those namespaces. nodes = assets[asset_name]["nodes"] nodes = [ node for node in nodes if commands.get_namespace_from_node(node) in namespaces ] assets[asset_name]["nodes"] = nodes return assets def select_asset_from_items(self): """Select nodes from listed asset""" items = self.get_nodes(selection=False) nodes = [] for item in items.values(): nodes.extend(item["nodes"]) commands.select(nodes) def right_mouse_menu(self, pos): """Build RMB menu for asset outliner""" active = self.view.currentIndex() # index under mouse active = active.sibling(active.row(), 0) # get first column globalpos = self.view.viewport().mapToGlobal(pos) menu = QtWidgets.QMenu(self.view) # Direct assignment apply_action = QtWidgets.QAction(menu, text="Select nodes") apply_action.triggered.connect(self.select_asset_from_items) if not active.isValid(): apply_action.setEnabled(False) menu.addAction(apply_action) menu.exec_(globalpos)
class AssetOutliner(QtWidgets.QWidget): refreshed = QtCore.Signal() selection_changed = QtCore.Signal() def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(10) title = QtWidgets.QLabel("Rigs:") title.setAlignment(QtCore.Qt.AlignLeft) title.setStyleSheet("font-weight: bold; font-size: 12px") model = models.AssetModel() view = views.View() view.setModel(model) view.setSortingEnabled(False) view.setHeaderHidden(True) view.setMinimumHeight(180) view.setIndentation(10) layout.addWidget(title) layout.addWidget(view) selection_model = view.selectionModel() selection_model.selectionChanged.connect(self.selection_changed) self.view = view self.model = model self._selection_model = selection_model self.setLayout(layout) def clear(self): self.model.clear() # fix looks remaining visible when no items present after "refresh" # todo: figure out why this workaround is needed. self.selection_changed.emit() def clear_selection(self): flags = self._selection_model.Clear self._selection_model.select(QtCore.QModelIndex(), flags) def select_index(self, index, flags=None): flags = flags or self._selection_model.ClearAndSelect self._selection_model.select(index, flags) def add_items(self, items): """Add new items to the outliner""" self.model.add_items(items) self.refreshed.emit() def get_selection_model(self): return self.view.selectionModel() def get_selected_items(self): """Get current selected items from view Returns: list: list of dictionaries """ selection_model = self.view.selectionModel() items = [row.data(NODEROLE) for row in selection_model.selectedRows(0)] return items def get_all_assets(self): """Add all items from the current scene""" with preserve_expanded_rows(self.view): with preserve_selection(self.view): self.clear() containers = lib.get_containers() items = lib.create_node(containers) self.add_items(items) return len(items) > 0 def get_selected_assets(self): """Add all selected items from the current scene""" raise NotImplementedError def get_nodes(self, selection=False): """Find the nodes in the current scene per asset.""" raise NotImplementedError items = self.get_selected_items() # Collect all nodes by hash (optimization) # if not selection: # nodes = cmds.ls(dag=True, long=True) # else: # nodes = commands.get_selected_nodes() # id_nodes = commands.create_asset_id_hash(nodes) # # # Collect the asset item entries per asset # # and collect the namespaces we'd like to apply # assets = dict() # asset_namespaces = defaultdict(set) # for item in items: # asset_id = str(item["asset"]["_id"]) # asset_name = item["asset"]["name"] # asset_namespaces[asset_name].add(item.get("namespace")) # # if asset_name in assets: # continue # # assets[asset_name] = item # assets[asset_name]["nodes"] = id_nodes.get(asset_id, []) # # # Filter nodes to namespace (if only namespaces were selected) # for asset_name in assets: # namespaces = asset_namespaces[asset_name] # # # When None is present there should be no filtering # if None in namespaces: # continue # # # Else only namespaces are selected and *not* the top entry so # # we should filter to only those namespaces. # nodes = assets[asset_name]["nodes"] # nodes = [node for node in nodes if # commands.get_namespace_from_node(node) in namespaces] # assets[asset_name]["nodes"] = nodes # # return assets def select_asset_from_items(self): """Select nodes from listed asset""" # items = self.get_nodes(selection=False) # nodes = [] # for item in items.values(): # nodes.extend(item["nodes"]) # # commands.select(nodes) raise NotImplementedError
class HostSelectorWidget(QtWidgets.QWidget): container_picked = QtCore.Signal(dict) host_selected = QtCore.Signal() def __init__(self, parent=None): super(HostSelectorWidget, self).__init__(parent=parent) panel = { "selection": QtWidgets.QWidget(), "container": QtWidgets.QWidget(), } model = { "containerModel": models.HostContainerListModel(), } widget = { "selectionChk": QtWidgets.QCheckBox("Use Selection"), "selectionBtn": QtWidgets.QPushButton("Compare Selection"), "containerChk": QtWidgets.QCheckBox("Use Container"), "containerBox": QtWidgets.QComboBox(), } widget["containerBox"].setModel(model["containerModel"]) layout = QtWidgets.QVBoxLayout(panel["selection"]) layout.addWidget(widget["selectionChk"]) layout.addWidget(widget["selectionBtn"]) layout = QtWidgets.QVBoxLayout(panel["container"]) layout.addWidget(widget["containerChk"]) layout.addWidget(widget["containerBox"]) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(panel["selection"]) layout.addWidget(panel["container"]) # Connect widget["selectionChk"].stateChanged.connect(self.on_use_selection) widget["containerChk"].stateChanged.connect(self.on_use_container) widget["selectionBtn"].pressed.connect(self.on_selection_pressed) widget["containerBox"].currentIndexChanged.connect( self.on_container_picked) # Init self.widget = widget self.model = model widget["containerChk"].setCheckState(QtCore.Qt.Checked) # Confirm host registered if not has_host(): # Disable all widgets for widget in self.widget.values(): widget.setEnabled(False) def build_container_list(self): self.model["containerModel"].reset() self.widget["containerBox"].setCurrentIndex(0) def on_use_selection(self, state): inverse = QtCore.Qt.Checked if not state else QtCore.Qt.Unchecked self.widget["containerChk"].blockSignals(True) self.widget["containerChk"].setCheckState(inverse) self.widget["containerChk"].blockSignals(False) self.widget["selectionBtn"].setEnabled(state) self.widget["containerBox"].setEnabled(not state) if not state: self.build_container_list() def on_use_container(self, state): inverse = QtCore.Qt.Checked if not state else QtCore.Qt.Unchecked self.widget["selectionChk"].blockSignals(True) self.widget["selectionChk"].setCheckState(inverse) self.widget["selectionChk"].blockSignals(False) self.widget["selectionBtn"].setEnabled(not state) self.widget["containerBox"].setEnabled(state) if state: self.build_container_list() def on_container_picked(self): container = self.widget["containerBox"].currentData() if container is not None: self.container_picked.emit(container) def on_selection_pressed(self): self.host_selected.emit()
class DatabaseSelectorWidget(QtWidgets.QWidget): version_changed = QtCore.Signal(io.ObjectId) def __init__(self, parent=None): super(DatabaseSelectorWidget, self).__init__(parent=parent) panel = { "silo": QtWidgets.QWidget(), "asset": QtWidgets.QWidget(), "subset": QtWidgets.QWidget(), "version": QtWidgets.QWidget(), } label = { "silo": QtWidgets.QLabel("Silo"), "asset": QtWidgets.QLabel("Asset"), "subset": QtWidgets.QLabel("Subset"), "version": QtWidgets.QLabel("Version"), } widget = { "silo": QtWidgets.QComboBox(), "asset": QtWidgets.QComboBox(), "subset": QtWidgets.QComboBox(), "version": QtWidgets.QComboBox(), } model = { "silo": models.DatabaseDocumentModel(level="silo"), "asset": models.DatabaseDocumentModel(level="asset"), "subset": models.DatabaseDocumentModel(level="subset"), "version": models.DatabaseDocumentModel(level="version"), } view = { "silo": QtWidgets.QListView(), "asset": QtWidgets.QListView(), "subset": QtWidgets.QListView(), "version": QtWidgets.QListView(), } widget["silo"].setModel(model["silo"]) widget["asset"].setModel(model["asset"]) widget["subset"].setModel(model["subset"]) widget["version"].setModel(model["version"]) widget["silo"].setView(view["silo"]) widget["asset"].setView(view["asset"]) widget["subset"].setView(view["subset"]) widget["version"].setView(view["version"]) def build_panel(level): label[level].setFixedWidth(60) label[level].setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight) layout = QtWidgets.QHBoxLayout(panel[level]) layout.addWidget(label[level]) layout.addWidget(widget[level]) build_panel("silo") build_panel("asset") build_panel("subset") build_panel("version") layout = QtWidgets.QVBoxLayout(self) layout.addWidget(panel["silo"]) layout.addSpacing(-16) layout.addWidget(panel["asset"]) layout.addSpacing(-16) layout.addWidget(panel["subset"]) layout.addSpacing(-16) layout.addWidget(panel["version"]) # Connect def connect_index_changed(level, callback): widget[level].currentIndexChanged.connect(callback) connect_index_changed("silo", self.on_silo_changed) connect_index_changed("asset", self.on_asset_changed) connect_index_changed("subset", self.on_subset_changed) connect_index_changed("version", self.on_version_changed) # Init self.widget = widget self.model = model self.view = view silo = api.Session.get("AVALON_SILO") if silo: init_index = self.widget["silo"].findText(silo) self.widget["silo"].setCurrentIndex(init_index) asset = api.Session.get("AVALON_ASSET") if silo and asset: init_index = self.widget["asset"].findText(asset) self.widget["asset"].setCurrentIndex(init_index) def _on_level_changed(self, level, child_level): combobox = self.widget[level] child_model = self.model[child_level] child_box = self.widget[child_level] data = combobox.currentData() child_model.reset(data) child_box.setCurrentIndex(0) def on_silo_changed(self): self._on_level_changed("silo", "asset") def on_asset_changed(self): self._on_level_changed("asset", "subset") def on_subset_changed(self): self._on_level_changed("subset", "version") def on_version_changed(self): combobox = self.widget["version"] data = combobox.currentData() if data: self.version_changed.emit(data) def on_container_picked(self, container): if container is None: return asset = io.find_one({"_id": io.ObjectId(container["assetId"])}) if asset is None: main_logger.error("Asset not found.") return self.widget["silo"].setCurrentText(asset["silo"]) self.widget["asset"].setCurrentText(asset["name"]) subset = io.find_one({"_id": io.ObjectId(container["subsetId"])}) if subset is None: main_logger.error("Subset not found.") return self.widget["subset"].setCurrentText(subset["name"])
class StatusLineWidget(QtWidgets.QWidget): """Logging message receiver and displayer Args: logger: `logging.Logger` instance """ echo = QtCore.Signal(int, str, int) def __init__(self, logger, parent=None): super(StatusLineWidget, self).__init__(parent) icon = _LogLevelIconButton() line = QtWidgets.QLineEdit() line.setReadOnly(True) line.setStyleSheet(""" QLineEdit { border: 0px; border-radius: 6px; padding: 0 6px; color: #AAAAAA; background: #363636; } """) body = QtWidgets.QHBoxLayout(self) body.addWidget(icon) body.addWidget(line) handler = _WidgetLogHandler(self) logger.addHandler(handler) self.icon = icon self.line = line self.echo.connect(self.on_echo) self.icon.clicked.connect(lambda: self._echo(0, "", True)) def _echo(self, level, log, reset=False): if reset: self.icon.previous = logging.NOTSET self.icon.level = level self.icon.update() self.line.setText(log) def on_echo(self, level, log, dotting=False): ALARM = logging.WARNING if level >= ALARM and level > self.icon.previous: self.icon.previous = level if dotting: # Display dotting animation if log.endswith("....."): log = log[:-4] else: log += "." self._echo(level, log) # Loop animator = (lambda: self.on_echo(level, log, dotting)) tools.lib.schedule(animator, 300, channel="statusline") else: self._echo(level, log) if level < ALARM: # Back to default state job = (lambda: self._echo(0, "")) else: # Keep alarm job = (lambda: self._echo(level, log)) tools.lib.schedule(job, 5000, channel="statusline")
class Popup(QtWidgets.QDialog): on_show = QtCore.Signal() def __init__(self, parent=None, *args, **kwargs): super(Popup, self).__init__(parent=parent, *args, **kwargs) self.setContentsMargins(0, 0, 0, 0) # Layout layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(10, 5, 10, 10) message = QtWidgets.QLabel("") message.setStyleSheet(""" QLabel { font-size: 12px; } """) show = QtWidgets.QPushButton("Show") show.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) show.setStyleSheet("""QPushButton { background-color: #BB0000 }""") layout.addWidget(message) layout.addWidget(show) # Size self.resize(400, 40) geometry = self.calculate_window_geometry() self.setGeometry(geometry) self.widgets = { "message": message, "show": show, } # Signals show.clicked.connect(self._on_show_clicked) # Set default title self.setWindowTitle("Popup") def setMessage(self, message): self.widgets['message'].setText(message) def _on_show_clicked(self): """Callback for when the 'show' button is clicked. Raises the parent (if any) """ parent = self.parent() self.close() # Trigger the signal self.on_show.emit() if parent: parent.raise_() def calculate_window_geometry(self): """Respond to status changes On creation, align window with screen bottom right. """ window = self width = window.width() width = max(width, window.minimumWidth()) height = window.height() height = max(height, window.sizeHint().height()) desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry() screen_geometry = window.geometry() screen_width = screen_geometry.width() screen_height = screen_geometry.height() # Calculate width and height of system tray systray_width = screen_geometry.width() - desktop_geometry.width() systray_height = screen_geometry.height() - desktop_geometry.height() padding = 10 x = screen_width - width y = screen_height - height x -= systray_width + padding y -= systray_height + padding return QtCore.QRect(x, y, width, height)
class AssetOutliner(QtWidgets.QWidget): refreshed = QtCore.Signal() selection_changed = QtCore.Signal() def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) layout = QtWidgets.QVBoxLayout() title = QtWidgets.QLabel("Assets") title.setAlignment(QtCore.Qt.AlignCenter) title.setStyleSheet("font-weight: bold; font-size: 12px") model = models.AssetModel() view = views.View() view.setModel(model) view.customContextMenuRequested.connect(self.right_mouse_menu) view.setSortingEnabled(False) view.setHeaderHidden(False) view.setIndentation(10) asset_all = QtWidgets.QPushButton("All Loaded") asset_sel = QtWidgets.QPushButton("From Selection") asset_all.setCheckable(True) asset_sel.setCheckable(True) asset_group = QtWidgets.QButtonGroup(self) asset_group.addButton(asset_all) asset_group.addButton(asset_sel) lister_layout = QtWidgets.QHBoxLayout() lister_layout.addWidget(asset_all) lister_layout.addWidget(asset_sel) layout.addWidget(title) layout.addLayout(lister_layout) layout.addWidget(view) # Build connections asset_all.clicked.connect(self.on_all_loaded) asset_sel.clicked.connect(self.on_selection) selection_model = view.selectionModel() selection_model.selectionChanged.connect(self.selection_changed) self.view = view self.model = model self.setLayout(layout) self.log = logging.getLogger(__name__) def clear(self): self.model.clear() # fix looks remaining visible when no items present after "refresh" # todo: figure out why this workaround is needed. self.selection_changed.emit() def add_items(self, items, by_selection=False): """Add new items to the outliner""" self.model.add_items(items, by_selection) self.refreshed.emit() def get_selected_items(self): """Get current selected items from view Returns: list: list of dictionaries """ selection_model = self.view.selectionModel() items = [ self.model.data(index, self.model.ItemRole) for index in selection_model.selectedRows(0) ] return items def on_all_loaded(self): """Add all items from the current scene""" items = list() with lib.preserve_expanded_rows(self.view): with lib.preserve_selection(self.view): self.clear() nodes = commands.get_all_asset_nodes() items += commands.create_items(nodes) self.add_items(items) return len(items) > 0 def on_selection(self): """Add all selected items from the current scene""" with lib.preserve_expanded_rows(self.view): with lib.preserve_selection(self.view): self.clear() nodes = commands.get_selected_asset_nodes() items = commands.create_items(nodes, by_selection=True) self.add_items(items, by_selection=True) def get_nodes(self): """Find the nodes in the current scene per asset.""" asset_nodes = dict() for item in self.get_selected_items(): asset_name = item["asset"]["name"] if "subset" in item: key = (asset_name, item["namespace"]) asset_nodes[key] = item else: for child in item.children(): key = (asset_name, child["namespace"]) asset_nodes[key] = child return asset_nodes def select_asset_from_items(self): asset_nodes = self.get_nodes() nodes = set() for item in asset_nodes.values(): nodes.update(item["selectBack"]) commands.select(list(nodes)) def remove_look_from_items(self): asset_nodes = self.get_nodes() nodes = [] asset_ids = set() for item in asset_nodes.values(): nodes.extend(item["nodes"]) asset_ids.add(str(item["asset"]["_id"])) commands.remove_look(nodes, asset_ids) def right_mouse_menu(self, pos): """Build RMB menu for asset outliner""" active = self.view.currentIndex() # index under mouse active = active.sibling(active.row(), 0) # get first column globalpos = self.view.viewport().mapToGlobal(pos) menu = QtWidgets.QMenu(self.view) apply_action = QtWidgets.QAction(menu, text="Select nodes") apply_action.triggered.connect(self.select_asset_from_items) remove_action = QtWidgets.QAction(menu, text="Remove look") remove_action.triggered.connect(self.remove_look_from_items) if not active.isValid(): apply_action.setEnabled(False) remove_action.setEnabled(False) menu.addAction(apply_action) menu.addAction(remove_action) menu.exec_(globalpos)
class AddTaskIconDialog(QtWidgets.QWidget): addTask = QtCore.Signal(dict) def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent=parent) self.setWindowFlag(QtCore.Qt.Dialog) if parent: self.setStyleSheet(parent.styleSheet()) layout = QtWidgets.QVBoxLayout() task_icon_layout = QtWidgets.QHBoxLayout() task_name = QtWidgets.QLineEdit() icon_name = QtWidgets.QComboBox() icon_preview_button = QtWidgets.QPushButton() icon_preview_button.setEnabled(False) icon_preview_button.setStyleSheet(style.preview_button) task_icon_layout.addWidget(task_name) task_icon_layout.addWidget(icon_name) task_icon_layout.addWidget(icon_preview_button) buttons_layout = QtWidgets.QHBoxLayout() accept_button = QtWidgets.QPushButton("Accept") cancel_button = QtWidgets.QPushButton("Cancel") buttons_layout.addWidget(accept_button) buttons_layout.addWidget(cancel_button) layout.addLayout(task_icon_layout) layout.addLayout(buttons_layout) self.setLayout(layout) self.task_name = task_name self.icon_name = icon_name self.icon_preview = icon_preview_button self.fontlib = {} self.connect_signals() self.refresh() def refresh(self): self._install_fontlib() self._populate_icons() def _install_fontlib(self): # TODO: fix qta thingy package = os.path.dirname(qta.__file__) fonts = os.path.join(package, "fonts", "fontawesome-webfont-charmap.json") with open(fonts, "r") as f: font_lib = json.load(f) self.fontlib = font_lib def _populate_icons(self): self.icon_name.blockSignals(True) icons = [""] + list(self.fontlib.keys()) self.icon_name.addItems(icons) self.icon_name.blockSignals(False) def connect_signals(self): return def on_accept(self): # TODO: make preflight check self.addTask.emit({ "name": self.task_name.currentText(), "icon": self.icon_name.currentText() }) self.close() def _create_preview(self): icon_name = "fa.{}".format(self.icon_name.currentText()) new_icon = qta.icon(icon_name, color=style.colors.dark) self.icon_preview.setIcon(new_icon)
class LookOutliner(QtWidgets.QWidget): menu_apply_action = QtCore.Signal() def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) # look manager layout layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(10) # Looks from database title = QtWidgets.QLabel("Looks") title.setAlignment(QtCore.Qt.AlignCenter) title.setStyleSheet("font-weight: bold; font-size: 12px") title.setAlignment(QtCore.Qt.AlignCenter) model = models.LookModel() # Proxy for dynamic sorting proxy = QtCore.QSortFilterProxyModel() proxy.setSourceModel(model) view = views.View() view.setModel(proxy) view.setMinimumHeight(180) view.setToolTip("Use right mouse button menu for direct actions") view.customContextMenuRequested.connect(self.right_mouse_menu) view.sortByColumn(0, QtCore.Qt.AscendingOrder) layout.addWidget(title) layout.addWidget(view) self.view = view self.model = model def clear(self): self.model.clear() def add_items(self, items): self.model.add_items(items) def get_selected_items(self): """Get current selected items from view Returns: list: list of dictionaries """ datas = [i.data(TreeModel.ItemRole) for i in self.view.get_indices()] items = [d for d in datas if d is not None] # filter Nones return items def right_mouse_menu(self, pos): """Build RMB menu for look view""" active = self.view.currentIndex() # index under mouse active = active.sibling(active.row(), 0) # get first column globalpos = self.view.viewport().mapToGlobal(pos) if not active.isValid(): return menu = QtWidgets.QMenu(self.view) # Direct assignment apply_action = QtWidgets.QAction(menu, text="Assign looks..") apply_action.triggered.connect(self.menu_apply_action) menu.addAction(apply_action) menu.exec_(globalpos)
class ComparingTable(QtWidgets.QWidget): picked = QtCore.Signal(str, dict) focus_enabled = QtCore.Signal(int) def __init__(self, parent=None): super(ComparingTable, self).__init__(parent=parent) data = { "model": models.ComparerModel(), "proxy": QtCore.QSortFilterProxyModel(), "view": QtWidgets.QTreeView(), "diff": delegates.DiffDelegate(), "path": delegates.PathTextDelegate(), } data["view"].setIndentation(2) data["view"].setStyleSheet(""" QTreeView::item{ padding: 4px 1px; border: 0px; } """) data["view"].setContextMenuPolicy(QtCore.Qt.CustomContextMenu) data["view"].setAllColumnsShowFocus(False) data["view"].setAlternatingRowColors(False) data["view"].setSortingEnabled(True) data["view"].setSelectionBehavior( QtWidgets.QAbstractItemView.SelectItems) data["view"].setSelectionMode( QtWidgets.QAbstractItemView.ExtendedSelection) # Delegate diff_delegate = data["diff"] column = data["model"].Columns.index("diff") data["view"].setItemDelegateForColumn(column, diff_delegate) path_delegate = data["path"] column = data["model"].Columns.index(SIDE_A) data["view"].setItemDelegateForColumn(column, path_delegate) column = data["model"].Columns.index(SIDE_B) data["view"].setItemDelegateForColumn(column, path_delegate) # Set Model data["proxy"].setSourceModel(data["model"]) data["view"].setModel(data["proxy"]) layout = QtWidgets.QHBoxLayout(self) layout.addWidget(data["view"]) # Init header = data["view"].header() header.setMinimumSectionSize(data["diff"].ICON_SPACE) header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed) header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) header.setSectionsMovable(False) data["view"].setColumnWidth(1, data["diff"].ICON_SPACE) self.data = data self._focusing = False # Connect data["view"].customContextMenuRequested.connect(self.on_context_menu) data["view"].clicked.connect(self.on_focused) self.focus_enabled.connect(self.on_focus_enabled) def on_context_menu(self, point): point_index = self.data["view"].indexAt(point) if not point_index.isValid(): return menu = QtWidgets.QMenu(self) if lib.select_from_host is not NotImplemented: select_action = QtWidgets.QAction("Select", menu) select_action.triggered.connect(self.act_select_nodes) menu.addAction(select_action) # Show the context action menu global_point = self.data["view"].mapToGlobal(point) action = menu.exec_(global_point) if not action: return def on_version_changed(self, side, version_id): if version_id is not None: profile = lib.profile_from_database(version_id) else: profile = None self.data["model"].refresh_side(side, profile) self.update() def on_container_picked(self, side, container): if container is not None: profile = lib.profile_from_host(container) else: profile = None self.data["model"].refresh_side(side, profile, host=True) self.update() def on_host_selected(self, side): profile = lib.profile_from_host() self.data["model"].refresh_side(side, profile, host=True) self.update() def act_select_nodes(self): selection_model = self.data["view"].selectionModel() selection = selection_model.selection() source_selection = self.data["proxy"].mapSelectionToSource(selection) model = self.data["model"] names = list() for index in source_selection.indexes(): name = index.data(model.HostSelectRole) if name is not None: names.append(name) lib.select_from_host(names) def on_focus_enabled(self, enable): if not enable: self.data["model"].set_fouced(SIDE_A, None) self.data["model"].set_fouced(SIDE_B, None) self._focusing = enable def on_focused(self, index): if not self._focusing: return column = index.column() if column == 1: # Diff section return side = SIDE_A if column == 0 else SIDE_B side_data = models.SIDE_A_DATA if column == 0 else models.SIDE_B_DATA selection_model = self.data["view"].selectionModel() selection = selection_model.selection() selected = index in selection.indexes() if selected: node = index.data(models.ComparerModel.ItemRole) data = node.get(side_data) index = self.data["proxy"].mapToSource(index) if data else None self.data["model"].set_fouced(side, index) else: data = None self.data["model"].set_fouced(side, None) self.picked.emit(side, data) self.update() def table_to_dict(self): return self.data["model"].to_dict()
class Popup(QtWidgets.QDialog): """A Popup that moves itself to bottom right of screen on show event. The UI contains a message label and a red highlighted button to "show" or perform another custom action from this pop-up. """ on_clicked = QtCore.Signal() def __init__(self, parent=None, *args, **kwargs): super(Popup, self).__init__(parent=parent, *args, **kwargs) self.setContentsMargins(0, 0, 0, 0) # Layout layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(10, 5, 10, 10) # Increase spacing slightly for readability layout.setSpacing(10) message = QtWidgets.QLabel("") message.setStyleSheet(""" QLabel { font-size: 12px; } """) button = QtWidgets.QPushButton("Show") button.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) button.setStyleSheet("""QPushButton { background-color: #BB0000 }""") layout.addWidget(message) layout.addWidget(button) # Default size self.resize(400, 40) self.widgets = { "message": message, "button": button, } # Signals button.clicked.connect(self._on_clicked) # Set default title self.setWindowTitle("Popup") def setMessage(self, message): self.widgets['message'].setText(message) def setButtonText(self, text): self.widgets["button"].setText(text) def _on_clicked(self): """Callback for when the 'show' button is clicked. Raises the parent (if any) """ parent = self.parent() self.close() # Trigger the signal self.on_clicked.emit() if parent: parent.raise_() def showEvent(self, event): # Position popup based on contents on show event geo = self.calculate_window_geometry() self.setGeometry(geo) return super(Popup, self).showEvent(event) def calculate_window_geometry(self): """Respond to status changes On creation, align window with screen bottom right. """ window = self width = window.width() width = max(width, window.minimumWidth()) height = window.height() height = max(height, window.sizeHint().height()) desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry() screen_geometry = window.geometry() screen_width = screen_geometry.width() screen_height = screen_geometry.height() # Calculate width and height of system tray systray_width = screen_geometry.width() - desktop_geometry.width() systray_height = screen_geometry.height() - desktop_geometry.height() padding = 10 x = screen_width - width y = screen_height - height x -= systray_width + padding y -= systray_height + padding return QtCore.QRect(x, y, width, height)
class JobSourceModel(TreeModel): # QueueModel ? MAX_CONNECTIONS = 10 staging = QtCore.Signal() staged = QtCore.Signal() canceling = QtCore.Signal() canceled = QtCore.Signal() STAGING_COLUMNS = [ "project", "type", "description", "site", "count", "size", ] StagingDisplayRole = QtCore.Qt.UserRole + 10 StagingSortRole = QtCore.Qt.UserRole + 11 UPLOAD_COLUMNS = [ "project", "type", "description", "status", # This has been hidden "progress", ] UploadDisplayRole = QtCore.Qt.UserRole + 20 UploadSortRole = QtCore.Qt.UserRole + 21 UploadDecorationRole = QtCore.Qt.UserRole + 22 UploadErrorRole = QtCore.Qt.UserRole + 23 STATUS = [ "staging", "pending", "uploading", "errored", "completed", "endWithError", ] STATUS_ICON = [ ("meh-o", "#999999"), ("clock-o", "#95A1A5"), ("paper-plane", "#52D77B"), ("paper-plane", "#ECA519"), ("check-circle", "#5AB6E4"), ("warning", "#EC534E"), ] def __init__(self, parent=None): super(JobSourceModel, self).__init__(parent=parent) self.jobsref = WeakValueDictionary() self.pipe_in = Queue() self.pipe_out = Queue() self.producer = _PackageProducer() self.consumers = [_Uploader(self.pipe_in, self.pipe_out, id) for id in range(self.MAX_CONNECTIONS)] self.consume() self.status_icon = [ qtawesome.icon("fa.{}".format(icon), color=color) for icon, color in self.STATUS_ICON ] def is_staging(self): return self.producer.producing def is_uploading(self): return any(c.consuming for c in self.consumers) def stage(self, job_file): """ Args: job_file (str): JSON file """ self.staging.emit() # Start staging self.producer.start(resource=job_file, on_produce=self._append, on_complete=self.staged.emit) def _append(self, data): package = PackageItem(data) # Check duplicated all_nodes = self._root_item.children() if package in all_nodes: # If duplicated package has completed, allow to stage # again. find = list(reversed(all_nodes)) if find[find.index(package)]["status"] <= 2: return # Start root = QtCore.QModelIndex() last = self.rowCount(root) self.beginInsertRows(root, last, last) self.add_child(package) self.endInsertRows() def stop(self): """Stop all activities""" if self.producer.producing: self.producer.stop() for consumer in self.consumers: consumer.stop() if self.is_staging() or self.is_uploading(): # Wait till they both stopped. self.canceling.emit() while self.is_staging() or self.is_uploading(): pass self.canceled.emit() def pending(self, index, skip_exists): package = self.data(index, self.ItemRole) package["status"] = 1 self.dataChanged.emit(index, index, list()) for job in package.jobs: job.skip_exists = skip_exists self.pipe_in.put(job) self.jobsref[job._id] = job def requeue_failed(self, package): for job in package.jobs: if job.result in (0, 1): # (TODO) Maybe add another list attribute to hold failed # job in package ? continue # Reset job.transferred = 0 job.result = 0 # Requeue self.pipe_in.put(job) self.jobsref[job._id] = job def requeue_all(self, package): for job in package.jobs: # Reset job.transferred = 0 job.result = 0 # Requeue self.pipe_in.put(job) self.jobsref[job._id] = job def consume(self): for c in self.consumers: c.start() def update(): while True: id, progress, result, process_id = self.pipe_out.get() job = self.jobsref[id] job.transferred = progress job.result = result if result == 0: # Still uploading self.consumers[process_id].consuming = True else: # Upload completed or error occurred self.consumers[process_id].consuming = False updator = threading.Thread(target=update, daemon=True) updator.start() def clear_stage(self): all_nodes = self._root_item.children() if all(n.get("status", 0) == 0 for n in all_nodes): # All staged, clear all self.clear() return # Remove staged only root = QtCore.QModelIndex() for node in list(all_nodes): if node.get("status", 0) == 0: row = node.row() self.beginRemoveRows(root, row, row) all_nodes.remove(node) self.endRemoveRows() def columnCount(self, parent): return max(len(self.STAGING_COLUMNS), len(self.UPLOAD_COLUMNS)) def data(self, index, role): if not index.isValid(): return if role == self.StagingDisplayRole or role == self.StagingSortRole: node = index.internalPointer() column = index.column() key = self.STAGING_COLUMNS[column] return node.get(key, None) if role == self.UploadDisplayRole or role == self.UploadSortRole: node = index.internalPointer() column = index.column() key = self.UPLOAD_COLUMNS[column] value = node.get(key, None) if key == "progress": return value() return value if role == self.UploadDecorationRole: # Put icon to 'progress' column if index.column() == 4: node = index.internalPointer() status = node.get("status", 0) return self.status_icon[status] if role == self.UploadErrorRole: node = index.internalPointer() if any(job.result not in (0, 1) for job in node.jobs): return node # Only return package that has failed job else: return None return super(JobSourceModel, self).data(index, role) def setData(self, index, value, role): """Change the data on the nodes. Returns: bool: Whether the edit was successful """ if index.isValid(): if role == self.StagingDisplayRole: node = index.internalPointer() column = index.column() key = self.STAGING_COLUMNS[column] node[key] = value # passing `list()` for PyQt5 (see PYSIDE-462) self.dataChanged.emit(index, index, list()) return True # must return true if successful if role == self.UploadDisplayRole: node = index.internalPointer() column = index.column() key = self.UPLOAD_COLUMNS[column] node[key] = value # passing `list()` for PyQt5 (see PYSIDE-462) self.dataChanged.emit(index, index, list()) return True # must return true if successful return False