from functools import partial from copy import deepcopy from ..pyside import QtWidgets, QtCore, QtGui from maya import cmds from .. import clipboard from ..align import align_shapes, arrange_horizontal, arrange_vertical from ..arrayutils import ( move_elements_to_array_end, move_elements_to_array_begin, move_up_array_elements, move_down_array_elements) from ..dialog import ( SearchAndReplaceDialog, warning, SettingsPaster, get_image_path) from ..geometry import ( rect_symmetry, path_symmetry, get_shapes_bounding_rects, rect_top_left_symmetry) from ..optionvar import BG_LOCKED, TRIGGER_REPLACE_ON_MIRROR from ..path import format_path from ..qtutils import set_shortcut, get_cursor from ..shape import Shape, get_shape_rect_from_options from ..shapelibrary import ShapeLibraryMenu from ..stack import count_panels from ..templates import BUTTON, TEXT, BACKGROUND, SHAPE_BUTTON from .canvas import ShapeEditCanvas from .display import DisplayOptions from .menu import MenuWidget from .attributes import AttributeEditor DIRECTION_OFFSETS = { 'Left': (-1, 0), 'Right': (1, 0), 'Up': (0, -1), 'Down': (0, 1)} class PickerEditor(QtWidgets.QWidget): def __init__(self, document, parent=None): super(PickerEditor, self).__init__(parent, QtCore.Qt.Window) title = "Picker editor - " + document.data['general']['name'] self.setWindowTitle(title) self.document = document self.document.shapes_changed.connect(self.update) self.document.general_option_changed.connect(self.generals_modified) self.document.data_changed.connect(self.update) self.document.data_changed.connect(self.selection_changed) self.display_options = DisplayOptions() self.shape_canvas = ShapeEditCanvas( self.document, self.display_options) self.shape_canvas.callContextMenu.connect(self.call_context_menu) bg_locked = bool(cmds.optionVar(query=BG_LOCKED)) self.shape_canvas.set_lock_background_shape(bg_locked) self.shape_canvas.selectedShapesChanged.connect(self.selection_changed) self.shape_library_menu = ShapeLibraryMenu(self) self.shape_library_menu.path_selected.connect( self.create_library_shape) self.menu = MenuWidget(self.display_options) self.menu.copyRequested.connect(self.copy) self.menu.copySettingsRequested.connect(self.copy_settings) self.menu.deleteRequested.connect(self.shape_canvas.delete_selection) self.menu.pasteRequested.connect(self.paste) self.menu.pasteSettingsRequested.connect(self.paste_settings) self.menu.snapValuesChanged.connect(self.snap_value_changed) self.menu.buttonLibraryRequested.connect(self.call_library) self.menu.useSnapToggled.connect(self.use_snap) method = self.shape_canvas.set_lock_background_shape self.menu.lockBackgroundShapeToggled.connect(method) self.menu.undoRequested.connect(self.document.undo) self.menu.redoRequested.connect(self.document.redo) method = partial(self.create_shape, BUTTON) self.menu.addButtonRequested.connect(method) method = partial(self.create_shape, TEXT) self.menu.addTextRequested.connect(method) method = partial(self.create_shape, BACKGROUND, before=True, image=True) self.menu.addBackgroundRequested.connect(method) method = self.set_selection_move_down self.menu.moveDownRequested.connect(method) method = self.set_selection_move_up self.menu.moveUpRequested.connect(method) method = self.set_selection_on_top self.menu.onTopRequested.connect(method) method = self.set_selection_on_bottom self.menu.onBottomRequested.connect(method) self.menu.symmetryRequested.connect(self.do_symmetry) self.menu.searchAndReplaceRequested.connect(self.search_and_replace) self.menu.alignRequested.connect(self.align_selection) self.menu.arrangeRequested.connect(self.arrange_selection) self.menu.load_ui_states() set_shortcut("Ctrl+Z", self.shape_canvas, self.document.undo) set_shortcut("Ctrl+Y", self.shape_canvas, self.document.redo) set_shortcut("Ctrl+C", self.shape_canvas, self.copy) set_shortcut("Ctrl+V", self.shape_canvas, self.paste) set_shortcut("Ctrl+R", self.shape_canvas, self.search_and_replace) set_shortcut("del", self.shape_canvas, self.shape_canvas.delete_selection) set_shortcut("Ctrl+D", self.shape_canvas, self.deselect_all) set_shortcut("Ctrl+A", self.shape_canvas, self.select_all) set_shortcut("Ctrl+I", self.shape_canvas, self.invert_selection) set_shortcut("U", self.shape_canvas, self.update_targets_on_selection) set_shortcut("F", self.shape_canvas, self.shape_canvas.focus) for direction in ['Left', 'Right', 'Up', 'Down']: method = partial(self.move_selection, direction) shortcut = set_shortcut(direction, self.shape_canvas, method) shortcut.setAutoRepeat(True) self.attribute_editor = AttributeEditor(document, self.display_options) self.attribute_editor.optionSet.connect(self.option_set) self.attribute_editor.optionsSet.connect(self.options_set) self.attribute_editor.imageModified.connect(self.image_modified) self.attribute_editor.selectLayerContent.connect(self.select_layer) self.attribute_editor.panelDoubleClicked.connect( self.shape_canvas.select_panel_shapes) self.hlayout = QtWidgets.QHBoxLayout() self.hlayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize) self.hlayout.setContentsMargins(0, 0, 0, 0) self.hlayout.addWidget(self.shape_canvas) self.hlayout.addWidget(self.attribute_editor) self.vlayout = QtWidgets.QVBoxLayout(self) self.vlayout.setContentsMargins(0, 0, 0, 0) self.vlayout.setSpacing(0) self.vlayout.addWidget(self.menu) self.vlayout.addLayout(self.hlayout) def call_library(self, point): self.shape_library_menu.move(point) self.shape_library_menu.show() def panels_changed(self, panels): self.document.data['general']['panels'] = panels def panels_resized(self, panels): self.document.data['general']['panels'] = panels def copy(self): clipboard.set([ deepcopy(s.options) for s in self.shape_canvas.selection]) def copy_settings(self): if len(self.shape_canvas.selection) != 1: return warning('Copy settings', 'Please select only one shape') shape = self.shape_canvas.selection[0] clipboard.set_settings(deepcopy(shape.options)) def sizeHint(self): return QtCore.QSize(1300, 750) def paste(self): clipboad_copy = [deepcopy(s) for s in clipboard.get()] shapes = self.document.add_shapes(clipboad_copy) self.shape_canvas.selection.replace(shapes) self.shape_canvas.update_selection() self.document.record_undo() self.document.shapes_changed.emit() def paste_settings(self): dialog = SettingsPaster() if not dialog.exec_(): return settings = clipboard.get_settings() settings = {k: v for k, v in settings.items() if k in dialog.settings} for shape in self.shape_canvas.selection: shape.options.update(deepcopy(settings)) shape.rect = get_shape_rect_from_options(shape.options) shape.synchronize_image() shape.update_path() self.document.record_undo() self.document.shapes_changed.emit() self.selection_changed() self.shape_canvas.update_selection() self.shape_canvas.update() def deselect_all(self): self.shape_canvas.selection.clear() self.shape_canvas.update_selection() self.shape_canvas.update() def select_all(self): shapes = self.shape_canvas.list_shapes() self.shape_canvas.selection.add(shapes) self.shape_canvas.update_selection() self.shape_canvas.update() def invert_selection(self): self.shape_canvas.selection.invert(self.shape_canvas.shapes) if self.menu.lock_bg.isChecked(): shapes = [ s for s in self.shape_canvas.selection if not s.is_background()] self.shape_canvas.selection.set(shapes) self.shape_canvas.update_selection() self.shape_canvas.update() def use_snap(self, state): snap = self.menu.snap_values() if state else None self.shape_canvas.transform.snap = snap self.shape_canvas.update() def snap_value_changed(self): self.shape_canvas.transform.snap = self.menu.snap_values() self.shape_canvas.update() def generals_modified(self, _, key): if key == 'name': title = "Picker editor - " + self.document.data['general']['name'] self.setWindowTitle(title) def options_set(self, options, rect_update): for shape in self.shape_canvas.selection: shape.options.update(options) if rect_update: shape.rect = QtCore.QRectF( options['shape.left'], options['shape.top'], options['shape.width'], options['shape.height']) shape.update_path() self.shape_canvas.update() self.update_manipulator_rect() self.document.record_undo() self.document.shapes_changed.emit() def option_set(self, option, value): update_geometries = False update_selection = False rect_options = ( 'shape.top', 'shape.width', 'shape.height', 'shape.left') for shape in self.shape_canvas.selection: shape.options[option] = value if option in ('shape.path', 'shape'): if value == 'custom' and not shape.options['shape.path']: update_selection = True shape.update_path() shape.synchronize_image() update_geometries = True if option in rect_options: shape.rect = QtCore.QRectF( shape.options['shape.left'], shape.options['shape.top'], shape.options['shape.width'], shape.options['shape.height']) shape.update_path() shape.synchronize_image() update_geometries = True if update_selection: self.selection_changed() if update_geometries: self.update_manipulator_rect() if option == 'visibility_layer': self.layers_modified() else: self.document.shapes_changed.emit() self.document.record_undo() self.shape_canvas.update() def selection_changed(self): shapes = self.shape_canvas.selection options = [shape.options for shape in shapes] self.attribute_editor.set_options(options) def create_shapes(self, targets, use_clipboard_data=False): shapes = [] for target in targets: template = deepcopy(BUTTON) if use_clipboard_data: template.update(deepcopy(clipboard.get_settings())) template['action.targets'] = [target] shapes.append(Shape(template)) self.shape_canvas.drag_shapes = shapes def create_library_shape(self, path): options = deepcopy(SHAPE_BUTTON) options['shape.path'] = deepcopy(path) self.create_shape(options) def create_shape( self, template, before=False, position=None, targets=None, image=False): options = deepcopy(template) panel = self.shape_canvas.display_options.current_panel options['panel'] = max((panel, 0)) if image: filename = get_image_path(self, "Select background image.") if filename: filename = format_path(filename) options['image.path'] = filename qimage = QtGui.QImage(filename) options['image.width'] = qimage.size().width() options['image.height'] = qimage.size().height() options['shape.width'] = qimage.size().width() options['shape.height'] = qimage.size().height() options['bgcolor.transparency'] = 255 shape = Shape(options) if not position: center = self.shape_canvas.rect().center() center = self.shape_canvas.viewportmapper.to_units_coords(center) if not options['shape.path']: shape.rect.moveCenter(center) else: shape.rect.moveTopLeft(center - shape.bounding_rect().center()) else: tl = self.shape_canvas.viewportmapper.to_units_coords(position) shape.rect.moveTopLeft(tl) if targets: shape.set_targets(targets) shape.synchronize_rect() shape.update_path() shapes = self.document.add_shapes([shape.options], prepend=before) self.document.shapes_changed.emit() self.document.record_undo() self.shape_canvas.selection.replace(shapes) self.selection_changed() self.update_manipulator_rect() def update_targets_on_selection(self): if not self.shape_canvas.selection: return targets = cmds.ls(selection=True) for shape in self.shape_canvas.selection: shape.set_targets(targets) self.shape_canvas.update() self.document.shapes_changed.emit() self.document.record_undo() def update_targets(self, shape): shape.set_targets(cmds.ls(selection=True)) self.shape_canvas.update() self.document.shapes_changed.emit() self.document.record_undo() def image_modified(self): for shape in self.shape_canvas.selection: shape.synchronize_image() self.shape_canvas.update() def set_selection_move_on_stack(self, function, inplace=True): selected_ids = [s.options['id'] for s in self.shape_canvas.selection] all_ids = list(self.document.shapes_by_id) result = function(all_ids, selected_ids) if inplace: result = all_ids data = [self.document.shapes_by_id[id_].options for id_ in result] self.document.set_shapes_data(data) self.document.record_undo() self.document.shapes_changed.emit() self.shape_canvas.update() def set_selection_move_down(self): self.set_selection_move_on_stack(move_down_array_elements, True) def set_selection_move_up(self): self.set_selection_move_on_stack(move_up_array_elements, True) def set_selection_on_top(self): self.set_selection_move_on_stack(move_elements_to_array_end, False) def set_selection_on_bottom(self): self.set_selection_move_on_stack(move_elements_to_array_begin, False) def update_manipulator_rect(self): rect = get_shapes_bounding_rects(self.shape_canvas.selection) self.shape_canvas.manipulator.set_rect(rect) self.shape_canvas.update() def do_symmetry(self, horizontal=True): shapes = self.shape_canvas.selection.shapes for shape in shapes: if shape.options['shape'] == 'custom': path_symmetry( path=shape.options['shape.path'], horizontal=horizontal) rect_top_left_symmetry( rect=shape.rect, point=self.shape_canvas.manipulator.rect.center(), horizontal=horizontal) shape.synchronize_rect() shape.update_path() else: rect_symmetry( rect=shape.rect, point=self.shape_canvas.manipulator.rect.center(), horizontal=horizontal) shape.synchronize_rect() self.shape_canvas.update() self.document.shapes_changed.emit() if not cmds.optionVar(query=TRIGGER_REPLACE_ON_MIRROR): self.document.record_undo() return if not self.search_and_replace(): self.document.record_undo() self.attribute_editor.update() self.update_manipulator_rect() def search_and_replace(self): dialog = SearchAndReplaceDialog() if not dialog.exec_(): return False if dialog.filter == 0: # Search on all shapes. shapes = self.shape_canvas.shapes else: shapes = self.shape_canvas.selection pattern = dialog.search.text() replace = dialog.replace.text() for s in shapes: if not dialog.field: # Targets if not s.targets(): continue targets = [t.replace(pattern, replace) for t in s.targets()] s.options['action.targets'] = targets continue if dialog.field <= 2: key = ('text.content', 'image.path')[dialog.field - 1] result = s.options[key].replace(pattern, replace) s.options[key] = result else: # Command code for command in s.options['action.commands']: result = command['command'].replace(pattern, replace) command['command'] = result self.document.shape_changed.emit() self.document.record_undo() self.shape_canvas.update() return True def move_selection(self, direction): offset = DIRECTION_OFFSETS[direction] rect = self.shape_canvas.manipulator.rect reference_rect = QtCore.QRectF(rect) self.shape_canvas.transform.set_rect(rect) self.shape_canvas.transform.reference_rect = reference_rect self.shape_canvas.transform.shift( self.shape_canvas.selection.shapes, offset) for shape in self.shape_canvas.selection: shape.synchronize_rect() shape.update_path() self.shape_canvas.update() self.selection_changed() self.document.record_undo() self.document.shapes_changed.emit() def align_selection(self, direction): if not self.shape_canvas.selection: return align_shapes(self.shape_canvas.selection, direction) rect = get_shapes_bounding_rects(self.shape_canvas.selection) self.shape_canvas.manipulator.set_rect(rect) self.shape_canvas.update() self.selection_changed() self.document.record_undo() self.document.shapes_changed.emit() def arrange_selection(self, direction): if not self.shape_canvas.selection: return if direction == 'horizontal': arrange_horizontal(self.shape_canvas.selection) else: arrange_vertical(self.shape_canvas.selection) rect = get_shapes_bounding_rects(self.shape_canvas.selection) self.shape_canvas.manipulator.set_rect(rect) self.shape_canvas.update() self.selection_changed() self.document.record_undo() self.document.shapes_changed.emit() def call_context_menu(self, position): targets = cmds.ls(selection=True) button = QtWidgets.QAction('Add selection button', self) method = partial( self.create_shape, deepcopy(BUTTON), position=position, targets=targets) button.triggered.connect(method) template = deepcopy(BUTTON) template.update(clipboard.get_settings()) method = partial( self.create_shape, template, position=position, targets=targets) text = 'Add selection button (using settings clipboard)' button2 = QtWidgets.QAction(text, self) button2.triggered.connect(method) button3 = QtWidgets.QAction('Add selection multiple buttons', self) button3.triggered.connect(partial(self.create_shapes, targets)) button3.setEnabled(len(targets) > 1) text = 'Add selection multiple buttons (using settings clipboard)' button4 = QtWidgets.QAction(text, self) button4.triggered.connect(partial(self.create_shapes, targets, True)) button4.setEnabled(len(targets) > 1) cursor = get_cursor(self.shape_canvas) cursor = self.shape_canvas.viewportmapper.to_units_coords(cursor) hovered_shape = self.shape_canvas.get_hovered_shape(cursor) method = partial(self.update_targets, hovered_shape) text = 'Update targets' button5 = QtWidgets.QAction(text, self) button5.setEnabled(bool(hovered_shape)) button5.triggered.connect(method) button6 = QtWidgets.QAction('Clear children', self) button6.setEnabled(bool(self.shape_canvas.selection or hovered_shape)) method = partial(self.clear_children, hovered_shape) button6.triggered.connect(method) menu = QtWidgets.QMenu() menu.addAction(button) menu.addAction(button2) menu.addAction(button3) menu.addAction(button4) menu.addAction(button5) menu.addSection('Hierarchy') menu.addAction(button6) menu.addSection('Visibility Layers') layers = sorted(list({ s.visibility_layer() for s in self.document.shapes if s.visibility_layer()})) add_selection = QtWidgets.QMenu('Assign to layer', self) add_selection.setEnabled(bool(layers)) menu.addMenu(add_selection) for layer in layers: action = QtWidgets.QAction(layer, self) action.triggered.connect(partial(self.set_visibility_layer, layer)) add_selection.addAction(action) remove_selection = QtWidgets.QAction('Remove assigned layer', self) remove_selection.setEnabled(bool(self.shape_canvas.selection.shapes)) remove_selection.triggered.connect(self.set_visibility_layer) menu.addAction(remove_selection) create_layer = QtWidgets.QAction('Create layer from selection', self) create_layer.triggered.connect(self.create_visibility_layer) create_layer.setEnabled(bool(self.shape_canvas.selection.shapes)) menu.addAction(create_layer) menu.addSeparator() assign_to_panel = QtWidgets.QMenu('Assign to panel', self) for i in range(count_panels(self.document.data['general']['panels'])): action = QtWidgets.QAction(str(i + 1), self) action.triggered.connect(partial(self.assign_to_panel, i)) assign_to_panel.addAction(action) menu.addMenu(assign_to_panel) menu.exec_(self.shape_canvas.mapToGlobal(position)) def clear_children(self, hovered_shape): if hovered_shape and hovered_shape not in self.shape_canvas.selection: shapes = [hovered_shape] else: shapes = self.shape_canvas.selection for shape in shapes: shape.options['children'] = [] self.document.shapes_changed.emit() self.document.record_undo() def set_visibility_layer(self, layer=''): for shape in self.shape_canvas.selection: shape.options['visibility_layer'] = layer self.layers_modified() def assign_to_panel(self, panel): for shape in self.shape_canvas.selection: shape.options['panel'] = panel self.document.shapes_changed.emit() self.document.record_undo() self.document.sync_shapes_caches() self.shape_canvas.update_selection(False) def layers_modified(self): self.selection_changed() model = self.attribute_editor.generals.layers.model model.layoutAboutToBeChanged.emit() self.document.sync_shapes_caches() self.document.record_undo() self.document.shapes_changed.emit() model.layoutChanged.emit() def create_visibility_layer(self): text, result = QtWidgets.QInputDialog.getText( self, 'Create visibility layer', 'Layer name') if not text or not result: return for shape in self.shape_canvas.selection: shape.options['visibility_layer'] = text self.layers_modified() def select_layer(self, layer): self.shape_canvas.selection.set(self.document.shapes_by_layer[layer]) self.shape_canvas.update_selection() self.shape_canvas.update() self.selection_changed()