Update
This commit is contained in:
820
2023/scripts/animation_tools/dwpicker/designer/attributes.py
Normal file
820
2023/scripts/animation_tools/dwpicker/designer/attributes.py
Normal file
@@ -0,0 +1,820 @@
|
||||
import maya.cmds as cmds
|
||||
from functools import partial
|
||||
from ..pyside import QtCore, QtWidgets
|
||||
|
||||
from ..commands import (
|
||||
CommandsEditor, MenuCommandsEditor, GlobalCommandsEditor)
|
||||
from ..qtutils import VALIGNS, HALIGNS
|
||||
from .stackeditor import StackEditor
|
||||
from .layer import VisibilityLayersEditor
|
||||
from .patheditor import PathEditor
|
||||
from ..stack import ORIENTATIONS
|
||||
from ..widgets import (
|
||||
BoolCombo, BrowseEdit, ColorEdit, ChildrenWidget, IntEdit, FloatEdit,
|
||||
LayerEdit, TextEdit, Title, WidgetToggler, ZoomsLockedEditor)
|
||||
|
||||
|
||||
LEFT_CELL_WIDTH = 90
|
||||
SHAPE_TYPES = 'square', 'round', 'rounded_rect', 'custom'
|
||||
SPACES = 'world', 'screen'
|
||||
ANCHORS = 'top_left', 'top_right', 'bottom_left', 'bottom_right'
|
||||
|
||||
|
||||
class AttributeEditor(QtWidgets.QWidget):
|
||||
imageModified = QtCore.Signal()
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
optionsSet = QtCore.Signal(dict, bool) # all options, affect rect
|
||||
selectLayerContent = QtCore.Signal(str)
|
||||
panelDoubleClicked = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, document, display_options, parent=None):
|
||||
super(AttributeEditor, self).__init__(parent)
|
||||
self.document = document
|
||||
|
||||
self.generals = GeneralSettings(self.document, display_options)
|
||||
self.generals.panelDoubleClicked.connect(self.panel_double_clicked)
|
||||
mtd = self.selectLayerContent.emit
|
||||
self.generals.layers.selectLayerContent.connect(mtd)
|
||||
|
||||
self.shape = ShapeSettings()
|
||||
self.shape.optionSet.connect(self.optionSet.emit)
|
||||
self.shape.optionsSet.connect(self.optionsSet.emit)
|
||||
self.shape_toggler = WidgetToggler('Shape', self.shape)
|
||||
|
||||
self.image = ImageSettings()
|
||||
self.image.optionSet.connect(self.image_modified)
|
||||
self.image_toggler = WidgetToggler('Image', self.image)
|
||||
|
||||
self.appearence = AppearenceSettings()
|
||||
self.appearence.optionSet.connect(self.optionSet.emit)
|
||||
self.appearence_toggler = WidgetToggler('Appearence', self.appearence)
|
||||
|
||||
self.text = TextSettings()
|
||||
self.text.optionSet.connect(self.optionSet.emit)
|
||||
self.text_toggler = WidgetToggler('Text', self.text)
|
||||
|
||||
self.action = ActionSettings(document, display_options)
|
||||
self.action.optionSet.connect(self.optionSet.emit)
|
||||
self.action_toggler = WidgetToggler('Action', self.action)
|
||||
|
||||
self.widget = QtWidgets.QWidget()
|
||||
self.layout = QtWidgets.QVBoxLayout(self.widget)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addWidget(self.shape_toggler)
|
||||
self.layout.addWidget(self.shape)
|
||||
self.layout.addWidget(self.image_toggler)
|
||||
self.layout.addWidget(self.image)
|
||||
self.layout.addWidget(self.appearence_toggler)
|
||||
self.layout.addWidget(self.appearence)
|
||||
self.layout.addWidget(self.text_toggler)
|
||||
self.layout.addWidget(self.text)
|
||||
self.layout.addWidget(self.action_toggler)
|
||||
self.layout.addWidget(self.action)
|
||||
self.layout.addStretch(1)
|
||||
|
||||
self.scroll_area = QtWidgets.QScrollArea()
|
||||
self.scroll_area.setWidget(self.widget)
|
||||
|
||||
self.tab = QtWidgets.QTabWidget()
|
||||
self.tab.addTab(self.scroll_area, 'Shapes')
|
||||
self.tab.addTab(self.generals, 'Picker')
|
||||
|
||||
self.main_layout = QtWidgets.QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.addWidget(self.tab)
|
||||
|
||||
self.setFixedWidth(self.sizeHint().width() * 1.05)
|
||||
|
||||
def panel_double_clicked(self, panel):
|
||||
self.panelDoubleClicked.emit(panel - 1)
|
||||
|
||||
def set_options(self, options):
|
||||
self.blockSignals(True)
|
||||
self.shape.set_options(options)
|
||||
self.image.set_options(options)
|
||||
self.appearence.set_options(options)
|
||||
self.text.set_options(options)
|
||||
self.action.set_options(options)
|
||||
self.blockSignals(False)
|
||||
|
||||
def image_modified(self, option, value):
|
||||
self.optionSet.emit(option, value)
|
||||
self.imageModified.emit()
|
||||
|
||||
|
||||
class GeneralSettings(QtWidgets.QWidget):
|
||||
panelDoubleClicked = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, document, display_options, parent=None):
|
||||
super(GeneralSettings, self).__init__(parent)
|
||||
self.document = document
|
||||
self.display_options = display_options
|
||||
|
||||
self.document.general_option_changed.connect(self.update_options)
|
||||
self.document.data_changed.connect(self.update_options)
|
||||
|
||||
self.name = TextEdit()
|
||||
self.name.valueSet.connect(partial(self.set_general, 'name'))
|
||||
|
||||
self.zoom_locked = ZoomsLockedEditor(self.document)
|
||||
|
||||
self.orientation = QtWidgets.QComboBox()
|
||||
self.orientation.addItems(list(ORIENTATIONS))
|
||||
method = partial(self.set_general, 'panels.orientation')
|
||||
self.orientation.currentTextChanged.connect(method)
|
||||
|
||||
self.as_sub_tab = BoolCombo()
|
||||
method = partial(self.set_general, 'panels.as_sub_tab')
|
||||
self.as_sub_tab.valueSet.connect(method)
|
||||
|
||||
self.stack = StackEditor()
|
||||
self.stack.setMinimumHeight(100)
|
||||
self.stack.panelsChanged.connect(self.stack_changed)
|
||||
self.stack.panelSelected.connect(self.panel_selected)
|
||||
self.stack.panelDoubleClicked.connect(self.panelDoubleClicked.emit)
|
||||
|
||||
self.layers = VisibilityLayersEditor(self.document)
|
||||
self.commands = GlobalCommandsEditor()
|
||||
method = partial(self.set_general, 'menu_commands')
|
||||
self.commands.valueSet.connect(method)
|
||||
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
form_layout.setSpacing(0)
|
||||
form_layout.setContentsMargins(0, 0, 0, 0)
|
||||
form_layout.setHorizontalSpacing(5)
|
||||
form_layout.addRow('Picker Name', self.name)
|
||||
|
||||
form_layout_2 = QtWidgets.QFormLayout()
|
||||
form_layout_2.setSpacing(0)
|
||||
form_layout_2.setContentsMargins(0, 0, 0, 0)
|
||||
form_layout_2.setHorizontalSpacing(5)
|
||||
form_layout_2.addRow('Columns orientation', self.orientation)
|
||||
form_layout_2.addRow('Display panels as tab', self.as_sub_tab)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addLayout(form_layout)
|
||||
layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout.addWidget(Title('Picker Panels'))
|
||||
layout.addLayout(form_layout_2)
|
||||
layout.addWidget(self.stack)
|
||||
layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout.addWidget(Title('Panels Zoom Locked'))
|
||||
layout.addWidget(self.zoom_locked)
|
||||
layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout.addWidget(Title('Visibility Layers'))
|
||||
layout.addWidget(self.layers)
|
||||
layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout.addWidget(Title('Global Right Click Commands'))
|
||||
layout.addWidget(self.commands)
|
||||
layout.addStretch()
|
||||
self.update_options()
|
||||
|
||||
def panel_selected(self, index):
|
||||
self.display_options.current_panel = index - 1
|
||||
self.display_options.options_changed.emit()
|
||||
|
||||
def stack_changed(self):
|
||||
self.set_general('panels', self.stack.data)
|
||||
|
||||
def set_general(self, key, value):
|
||||
self.document.data['general'][key] = value
|
||||
self.document.general_option_changed.emit('attribute_editor', key)
|
||||
self.document.record_undo()
|
||||
if key == 'panels.orientation':
|
||||
self.stack.set_orientation(value)
|
||||
|
||||
def update_options(self, *_):
|
||||
self.block_signals(True)
|
||||
options = self.document.data['general']
|
||||
self.stack.set_data(options['panels'])
|
||||
self.stack.set_orientation(options['panels.orientation'])
|
||||
self.as_sub_tab.setCurrentText(str(options['panels.as_sub_tab']))
|
||||
self.orientation.setCurrentText(options['panels.orientation'])
|
||||
self.name.setText(options['name'])
|
||||
self.commands.set_options(options)
|
||||
self.block_signals(False)
|
||||
|
||||
def block_signals(self, state):
|
||||
widgets = (
|
||||
self.stack,
|
||||
self.as_sub_tab,
|
||||
self.orientation,
|
||||
self.name,
|
||||
self.commands)
|
||||
for widget in widgets:
|
||||
widget.blockSignals(state)
|
||||
|
||||
|
||||
class ShapeSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
optionsSet = QtCore.Signal(dict, bool) # all options, affect rect
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ShapeSettings, self).__init__(parent)
|
||||
self.id_ = QtWidgets.QLineEdit()
|
||||
self.id_.setReadOnly(True)
|
||||
|
||||
self.shape = QtWidgets.QComboBox()
|
||||
self.shape.addItems(SHAPE_TYPES)
|
||||
self.shape.currentIndexChanged.connect(self.shape_changed)
|
||||
self.path_editor = PathEditor(self)
|
||||
self.path_editor.pathEdited.connect(self.path_edited)
|
||||
self.path_editor.setVisible(False)
|
||||
self.path_editor.setEnabled(False)
|
||||
|
||||
self.panel = QtWidgets.QLineEdit()
|
||||
self.panel.setReadOnly(True)
|
||||
self.layer = LayerEdit()
|
||||
method = partial(self.optionSet.emit, 'visibility_layer')
|
||||
self.layer.valueSet.connect(method)
|
||||
|
||||
self.background = BoolCombo()
|
||||
method = partial(self.optionSet.emit, 'background')
|
||||
self.background.valueSet.connect(method)
|
||||
|
||||
self.ignored_by_focus = BoolCombo()
|
||||
method = partial(self.optionSet.emit, 'shape.ignored_by_focus')
|
||||
self.ignored_by_focus.valueSet.connect(method)
|
||||
|
||||
self.space = QtWidgets.QComboBox()
|
||||
self.space.addItems(SPACES)
|
||||
self.space.currentIndexChanged.connect(self.space_changed)
|
||||
|
||||
self.anchor = QtWidgets.QComboBox()
|
||||
self.anchor.addItems(ANCHORS)
|
||||
method = partial(self.optionSet.emit, 'shape.anchor')
|
||||
self.anchor.currentTextChanged.connect(method)
|
||||
|
||||
self.left = IntEdit()
|
||||
method = partial(self.optionSet.emit, 'shape.left')
|
||||
self.left.valueSet.connect(method)
|
||||
self.top = IntEdit()
|
||||
method = partial(self.optionSet.emit, 'shape.top')
|
||||
self.top.valueSet.connect(method)
|
||||
self.width = IntEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'shape.width')
|
||||
self.width.valueSet.connect(method)
|
||||
self.height = IntEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'shape.height')
|
||||
self.height.valueSet.connect(method)
|
||||
self.cornersx = IntEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'shape.cornersx')
|
||||
self.cornersx.valueSet.connect(method)
|
||||
self.cornersy = IntEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'shape.cornersy')
|
||||
self.cornersy.valueSet.connect(method)
|
||||
|
||||
layout1 = QtWidgets.QFormLayout()
|
||||
layout1.setSpacing(0)
|
||||
layout1.setContentsMargins(0, 0, 0, 0)
|
||||
layout1.setHorizontalSpacing(5)
|
||||
layout1.addRow('Id', self.id_)
|
||||
layout1.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout1.addRow(Title('Display'))
|
||||
layout1.addRow('Panel number', self.panel)
|
||||
layout1.addRow('Visibility layer', self.layer)
|
||||
layout1.addRow('Background', self.background)
|
||||
layout1.addRow('Ignored by focus', self.ignored_by_focus)
|
||||
layout1.addRow('Shape', self.shape)
|
||||
|
||||
layout2 = QtWidgets.QVBoxLayout()
|
||||
layout2.setSpacing(0)
|
||||
layout2.setContentsMargins(0, 0, 0, 0)
|
||||
layout2.addWidget(self.path_editor)
|
||||
|
||||
layout3 = QtWidgets.QFormLayout()
|
||||
layout3.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout3.addRow(Title('Space'))
|
||||
layout3.addRow('space', self.space)
|
||||
layout3.addRow('anchor', self.anchor)
|
||||
layout3.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
layout3.addRow(Title('Dimensions'))
|
||||
layout3.addRow('left', self.left)
|
||||
layout3.addRow('top', self.top)
|
||||
layout3.addRow('width', self.width)
|
||||
layout3.addRow('height', self.height)
|
||||
layout3.addRow('roundness x', self.cornersx)
|
||||
layout3.addRow('roundness y', self.cornersy)
|
||||
layout3.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addLayout(layout1)
|
||||
layout.addLayout(layout2)
|
||||
layout.addLayout(layout3)
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def path_edited(self):
|
||||
if self.shape.currentText() != 'custom':
|
||||
return
|
||||
self.optionSet.emit('shape.path', self.path_editor.path())
|
||||
|
||||
def shape_changed(self, _):
|
||||
self.path_editor.setEnabled(self.shape.currentText() == 'custom')
|
||||
self.path_editor.setVisible(self.shape.currentText() == 'custom')
|
||||
self.height.setEnabled(self.shape.currentText() != 'custom')
|
||||
self.width.setEnabled(self.shape.currentText() != 'custom')
|
||||
self.optionSet.emit('shape', self.shape.currentText())
|
||||
self.path_editor.canvas.focus()
|
||||
|
||||
def space_changed(self, index):
|
||||
self.anchor.setEnabled(bool(index))
|
||||
self.optionSet.emit('shape.space', self.space.currentText())
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['id'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else '...'
|
||||
self.id_.setText(value)
|
||||
|
||||
values = list({option['background'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.background.setCurrentText(value)
|
||||
|
||||
values = list({option['shape.ignored_by_focus'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.ignored_by_focus.setCurrentText(value)
|
||||
|
||||
values = list({option['panel'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '' if not values else '...'
|
||||
self.panel.setText(str(value + 1 if isinstance(value, int) else value))
|
||||
|
||||
values = list({option['shape.space'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '' if not values else '...'
|
||||
self.space.setCurrentText(value)
|
||||
|
||||
values = list({option['shape.anchor'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '' if not values else '...'
|
||||
self.anchor.setCurrentText(value)
|
||||
self.anchor.setEnabled(self.space.currentText() == 'screen')
|
||||
|
||||
values = list({option['visibility_layer'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '' if not values else '...'
|
||||
self.layer.set_layer(value)
|
||||
|
||||
values = list({option['shape'] for option in options})
|
||||
value = values[0] if len(values) == 1 else '...'
|
||||
self.shape.blockSignals(True)
|
||||
self.shape.setCurrentText(value)
|
||||
self.shape.blockSignals(False)
|
||||
self.height.setEnabled('custom' not in values)
|
||||
self.width.setEnabled('custom' not in values)
|
||||
|
||||
if len(options) == 1:
|
||||
self.path_editor.setEnabled(options[0]['shape'] == 'custom')
|
||||
self.path_editor.setVisible(options[0]['shape'] == 'custom')
|
||||
self.path_editor.set_options(options[0])
|
||||
else:
|
||||
self.path_editor.setEnabled(False)
|
||||
self.path_editor.setVisible(False)
|
||||
self.path_editor.set_options(None)
|
||||
|
||||
values = list({int(round((option['shape.left']))) for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.left.setText(value)
|
||||
|
||||
values = list({option['shape.top'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.top.setText(value)
|
||||
|
||||
values = list({option['shape.width'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.width.setText(value)
|
||||
|
||||
values = list({option['shape.height'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.height.setText(value)
|
||||
|
||||
values = list({option['shape.cornersx'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.cornersx.setText(value)
|
||||
|
||||
values = list({option['shape.cornersy'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.cornersy.setText(value)
|
||||
|
||||
|
||||
class ImageSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ImageSettings, self).__init__(parent)
|
||||
self.path = BrowseEdit()
|
||||
self.path.valueSet.connect(partial(self.optionSet.emit, 'image.path'))
|
||||
|
||||
self.fit = BoolCombo(True)
|
||||
self.fit.valueSet.connect(partial(self.optionSet.emit, 'image.fit'))
|
||||
|
||||
self.ratio = BoolCombo(True)
|
||||
method = partial(self.optionSet.emit, 'image.ratio')
|
||||
self.ratio.valueSet.connect(method)
|
||||
|
||||
self.width = FloatEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'image.width')
|
||||
self.width.valueSet.connect(method)
|
||||
|
||||
self.height = FloatEdit(minimum=0)
|
||||
method = partial(self.optionSet.emit, 'image.height')
|
||||
self.height.valueSet.connect(method)
|
||||
|
||||
self.layout = QtWidgets.QFormLayout(self)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setHorizontalSpacing(5)
|
||||
self.layout.addRow('Path', self.path)
|
||||
self.layout.addRow('Fit to shape', self.fit)
|
||||
self.layout.addRow('Preserve ratio', self.ratio)
|
||||
self.layout.addRow('Width', self.width)
|
||||
self.layout.addRow('Height', self.height)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['image.path'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.path.set_value(value)
|
||||
|
||||
values = list({option['image.fit'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.fit.setCurrentText(value)
|
||||
|
||||
values = list({option['image.ratio'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.ratio.setCurrentText(value)
|
||||
|
||||
values = list({option['image.width'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.width.setText(value)
|
||||
|
||||
values = list({option['image.height'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.height.setText(value)
|
||||
|
||||
|
||||
class AppearenceSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(AppearenceSettings, self).__init__(parent)
|
||||
|
||||
self.border = BoolCombo(True)
|
||||
method = partial(self.optionSet.emit, 'border')
|
||||
self.border.valueSet.connect(method)
|
||||
|
||||
self.borderwidth_normal = FloatEdit(minimum=0.0)
|
||||
method = partial(self.optionSet.emit, 'borderwidth.normal')
|
||||
self.borderwidth_normal.valueSet.connect(method)
|
||||
|
||||
self.borderwidth_hovered = FloatEdit(minimum=0.0)
|
||||
method = partial(self.optionSet.emit, 'borderwidth.hovered')
|
||||
self.borderwidth_hovered.valueSet.connect(method)
|
||||
|
||||
self.borderwidth_clicked = FloatEdit(minimum=0.0)
|
||||
method = partial(self.optionSet.emit, 'borderwidth.clicked')
|
||||
self.borderwidth_clicked.valueSet.connect(method)
|
||||
|
||||
self.bordercolor_normal = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bordercolor.normal')
|
||||
self.bordercolor_normal.valueSet.connect(method)
|
||||
|
||||
self.bordercolor_hovered = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bordercolor.hovered')
|
||||
self.bordercolor_hovered.valueSet.connect(method)
|
||||
|
||||
self.bordercolor_clicked = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bordercolor.clicked')
|
||||
self.bordercolor_clicked.valueSet.connect(method)
|
||||
|
||||
self.bordercolor_transparency = IntEdit(minimum=0, maximum=255)
|
||||
method = partial(self.optionSet.emit, 'bordercolor.transparency')
|
||||
self.bordercolor_transparency.valueSet.connect(method)
|
||||
|
||||
self.backgroundcolor_normal = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bgcolor.normal')
|
||||
self.backgroundcolor_normal.valueSet.connect(method)
|
||||
|
||||
self.backgroundcolor_hovered = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bgcolor.hovered')
|
||||
self.backgroundcolor_hovered.valueSet.connect(method)
|
||||
|
||||
self.backgroundcolor_clicked = ColorEdit()
|
||||
method = partial(self.optionSet.emit, 'bgcolor.clicked')
|
||||
self.backgroundcolor_clicked.valueSet.connect(method)
|
||||
|
||||
self.backgroundcolor_transparency = IntEdit(minimum=0, maximum=255)
|
||||
method = partial(self.optionSet.emit, 'bgcolor.transparency')
|
||||
self.backgroundcolor_transparency.valueSet.connect(method)
|
||||
|
||||
self.layout = QtWidgets.QFormLayout(self)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setHorizontalSpacing(5)
|
||||
self.layout.addRow('border visible', self.border)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Border width (pxf)'))
|
||||
self.layout.addRow('normal', self.borderwidth_normal)
|
||||
self.layout.addRow('hovered', self.borderwidth_hovered)
|
||||
self.layout.addRow('clicked', self.borderwidth_clicked)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Border color'))
|
||||
self.layout.addRow('normal', self.bordercolor_normal)
|
||||
self.layout.addRow('hovered', self.bordercolor_hovered)
|
||||
self.layout.addRow('clicked', self.bordercolor_clicked)
|
||||
self.layout.addRow('transparency', self.bordercolor_transparency)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Background color'))
|
||||
self.layout.addRow('normal', self.backgroundcolor_normal)
|
||||
self.layout.addRow('hovered', self.backgroundcolor_hovered)
|
||||
self.layout.addRow('clicked', self.backgroundcolor_clicked)
|
||||
self.layout.addRow('transparency', self.backgroundcolor_transparency)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['border'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.border.setCurrentText(value)
|
||||
|
||||
values = list({option['borderwidth.normal'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.borderwidth_normal.setText(value)
|
||||
|
||||
values = list({option['borderwidth.hovered'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.borderwidth_hovered.setText(value)
|
||||
|
||||
values = list({option['borderwidth.clicked'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.borderwidth_clicked.setText(value)
|
||||
|
||||
values = list({option['bordercolor.normal'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bordercolor_normal.set_color(value)
|
||||
|
||||
values = list({option['bordercolor.hovered'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bordercolor_hovered.set_color(value)
|
||||
|
||||
values = list({option['bordercolor.clicked'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bordercolor_clicked.set_color(value)
|
||||
|
||||
values = list({option['bordercolor.transparency'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bordercolor_transparency.setText(value)
|
||||
|
||||
values = list({option['bgcolor.normal'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.backgroundcolor_normal.set_color(value)
|
||||
|
||||
values = list({option['bgcolor.hovered'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.backgroundcolor_hovered.set_color(value)
|
||||
|
||||
values = list({option['bgcolor.clicked'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.backgroundcolor_clicked.set_color(value)
|
||||
|
||||
values = list({option['bgcolor.transparency'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.backgroundcolor_transparency.setText(value)
|
||||
|
||||
|
||||
class ActionSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, document, display_options, parent=None):
|
||||
super(ActionSettings, self).__init__(parent)
|
||||
self._targets = QtWidgets.QLineEdit()
|
||||
self._targets.returnPressed.connect(self.targets_changed)
|
||||
|
||||
self._add_targets = QtWidgets.QPushButton('Add')
|
||||
self._add_targets.released.connect(self.call_add_targets)
|
||||
self._add_targets.setToolTip('Add selected objects')
|
||||
self._add_targets.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
self._remove_targets = QtWidgets.QPushButton('Remove')
|
||||
self._remove_targets.released.connect(self.call_remove_targets)
|
||||
self._remove_targets.setToolTip('Remove selected objects')
|
||||
self._remove_targets.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
self._replace_targets = QtWidgets.QPushButton('Replace')
|
||||
self._replace_targets.clicked.connect(self.call_replace_targets)
|
||||
self._replace_targets.setToolTip('Replace target by current selection')
|
||||
self._replace_targets.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
self._clear_targets = QtWidgets.QPushButton('Clear')
|
||||
self._clear_targets.clicked.connect(self.call_clear_targets)
|
||||
self._clear_targets.setToolTip('Clear shape targets')
|
||||
self._clear_targets.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
_targets = QtWidgets.QWidget()
|
||||
self._targets_layout = QtWidgets.QHBoxLayout(_targets)
|
||||
self._targets_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self._targets_layout.setSpacing(2)
|
||||
self._targets_layout.addStretch()
|
||||
self._targets_layout.addWidget(self._add_targets)
|
||||
self._targets_layout.addWidget(self._remove_targets)
|
||||
self._targets_layout.addWidget(self._replace_targets)
|
||||
self._targets_layout.addWidget(self._clear_targets)
|
||||
|
||||
self._commands = CommandsEditor()
|
||||
method = partial(self.optionSet.emit, 'action.commands')
|
||||
self._commands.valueSet.connect(method)
|
||||
|
||||
self._menu = MenuCommandsEditor()
|
||||
method = partial(self.optionSet.emit, 'action.menu_commands')
|
||||
self._menu.valueSet.connect(method)
|
||||
|
||||
self.select_one_shape_label = QtWidgets.QLabel('Select only one shape')
|
||||
self.children = ChildrenWidget(document, display_options)
|
||||
method = partial(self.optionSet.emit, 'children')
|
||||
self.children.children_changed.connect(method)
|
||||
|
||||
form = QtWidgets.QFormLayout()
|
||||
form.setSpacing(0)
|
||||
form.setContentsMargins(0, 0, 0, 0)
|
||||
form.setHorizontalSpacing(5)
|
||||
form.addRow(Title('Selection'))
|
||||
form.addRow('Targets', self._targets)
|
||||
form.addWidget(_targets)
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.addLayout(form)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
self.layout.addWidget(Title('Scripts'))
|
||||
self.layout.addWidget(self._commands)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addWidget(Title('Right Click Menu'))
|
||||
self.layout.addWidget(self._menu)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addWidget(Title('Hierarchy'))
|
||||
self.layout.addWidget(self.select_one_shape_label)
|
||||
self.layout.addWidget(self.children)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
def targets(self):
|
||||
targets = str(self._targets.text())
|
||||
try:
|
||||
return [t.strip(" ") for t in targets.split(',')]
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
def call_clear_targets(self):
|
||||
self._targets.setText('')
|
||||
self.targets_changed()
|
||||
|
||||
def call_add_targets(self):
|
||||
selection = cmds.ls(selection=True, flatten=True)
|
||||
if not selection:
|
||||
return
|
||||
|
||||
targets = self.targets()
|
||||
edits = [item for item in selection if item not in targets]
|
||||
targets = targets if targets != [''] else []
|
||||
self._targets.setText(', '.join(targets + edits))
|
||||
self.targets_changed()
|
||||
|
||||
def call_remove_targets(self):
|
||||
selection = cmds.ls(selection=True, flatten=True)
|
||||
if not selection:
|
||||
return
|
||||
|
||||
targets = [item for item in self.targets() if item not in selection]
|
||||
self._targets.setText(', '.join(targets))
|
||||
self.targets_changed()
|
||||
|
||||
def call_replace_targets(self):
|
||||
selection = cmds.ls(selection=True, flatten=True)
|
||||
if not selection:
|
||||
return
|
||||
self._targets.setText(', '.join(selection))
|
||||
self.targets_changed()
|
||||
|
||||
def targets_changed(self):
|
||||
values = [t.strip(" ") for t in self._targets.text().split(",")]
|
||||
self.optionSet.emit('action.targets', values)
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({o for opt in options for o in opt['action.targets']})
|
||||
self._targets.setText(", ".join(sorted(values)))
|
||||
self._commands.set_options(options)
|
||||
self._menu.set_options(options)
|
||||
self.select_one_shape_label.setVisible(len(options) != 1)
|
||||
self.children.clear()
|
||||
if len(options) == 1:
|
||||
self.children.set_children(options[0]['children'])
|
||||
|
||||
|
||||
class TextSettings(QtWidgets.QWidget):
|
||||
optionSet = QtCore.Signal(str, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(TextSettings, self).__init__(parent)
|
||||
self.text = TextEdit()
|
||||
method = partial(self.optionSet.emit, 'text.content')
|
||||
self.text.valueSet.connect(method)
|
||||
|
||||
self.size = FloatEdit(minimum=0.0)
|
||||
self.size.valueSet.connect(partial(self.optionSet.emit, 'text.size'))
|
||||
|
||||
self.bold = BoolCombo()
|
||||
self.bold.valueSet.connect(partial(self.optionSet.emit, 'text.bold'))
|
||||
|
||||
self.italic = BoolCombo()
|
||||
method = partial(self.optionSet.emit, 'text.italic')
|
||||
self.italic.valueSet.connect(method)
|
||||
|
||||
self.color = ColorEdit()
|
||||
self.color.valueSet.connect(partial(self.optionSet.emit, 'text.color'))
|
||||
|
||||
self.halignement = QtWidgets.QComboBox()
|
||||
self.halignement.addItems(HALIGNS.keys())
|
||||
self.halignement.currentIndexChanged.connect(self.halign_changed)
|
||||
self.valignement = QtWidgets.QComboBox()
|
||||
self.valignement.addItems(VALIGNS.keys())
|
||||
self.valignement.currentIndexChanged.connect(self.valign_changed)
|
||||
|
||||
self.layout = QtWidgets.QFormLayout(self)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setHorizontalSpacing(5)
|
||||
self.layout.addRow('Content', self.text)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Options'))
|
||||
self.layout.addRow('Size', self.size)
|
||||
self.layout.addRow('Bold', self.bold)
|
||||
self.layout.addRow('Italic', self.italic)
|
||||
self.layout.addRow('Color', self.color)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
self.layout.addRow(Title('Alignement'))
|
||||
self.layout.addRow('Horizontal', self.halignement)
|
||||
self.layout.addRow('Vertical', self.valignement)
|
||||
self.layout.addItem(QtWidgets.QSpacerItem(0, 8))
|
||||
|
||||
for label in self.findChildren(QtWidgets.QLabel):
|
||||
alignment = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
|
||||
if not isinstance(label, Title):
|
||||
label.setAlignment(alignment)
|
||||
label.setFixedWidth(LEFT_CELL_WIDTH)
|
||||
|
||||
def valign_changed(self):
|
||||
self.optionSet.emit('text.valign', self.valignement.currentText())
|
||||
|
||||
def halign_changed(self):
|
||||
self.optionSet.emit('text.halign', self.halignement.currentText())
|
||||
|
||||
def set_options(self, options):
|
||||
values = list({option['text.content'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.text.setText(value)
|
||||
|
||||
values = list({option['text.size'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.size.setText(value)
|
||||
|
||||
values = list({option['text.bold'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.bold.setCurrentText(value)
|
||||
|
||||
values = list({option['text.italic'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.italic.setCurrentText(value)
|
||||
|
||||
values = list({option['text.color'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.color.set_color(value)
|
||||
|
||||
values = list({option['text.halign'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.halignement.setCurrentText(value)
|
||||
|
||||
values = list({option['text.valign'] for option in options})
|
||||
value = str(values[0]) if len(values) == 1 else None
|
||||
self.valignement.setCurrentText(value)
|
||||
451
2023/scripts/animation_tools/dwpicker/designer/canvas.py
Normal file
451
2023/scripts/animation_tools/dwpicker/designer/canvas.py
Normal file
@@ -0,0 +1,451 @@
|
||||
from functools import partial
|
||||
from ..pyside import QtCore, QtGui, QtWidgets
|
||||
from maya import cmds
|
||||
|
||||
from ..align import align_shapes_on_line
|
||||
from ..interactive import Manipulator, SelectionSquare
|
||||
from ..interactionmanager import InteractionManager
|
||||
from ..optionvar import SNAP_GRID_X, SNAP_GRID_Y, SNAP_ITEMS
|
||||
from ..geometry import get_shapes_bounding_rects, get_connection_path
|
||||
from ..painting import (
|
||||
draw_editor_canvas, draw_shape, draw_manipulator, draw_selection_square,
|
||||
draw_parenting_shapes, draw_current_panel, draw_shape_as_child_background,
|
||||
draw_connections)
|
||||
from ..qtutils import get_cursor
|
||||
from ..selection import Selection, get_selection_mode
|
||||
from ..shape import cursor_in_shape
|
||||
from ..transform import Transform
|
||||
from ..viewport import ViewportMapper
|
||||
|
||||
|
||||
def load_saved_snap():
|
||||
if not cmds.optionVar(query=SNAP_ITEMS):
|
||||
return
|
||||
return (
|
||||
cmds.optionVar(query=SNAP_GRID_X),
|
||||
cmds.optionVar(query=SNAP_GRID_Y))
|
||||
|
||||
|
||||
class ShapeEditCanvas(QtWidgets.QWidget):
|
||||
selectedShapesChanged = QtCore.Signal()
|
||||
callContextMenu = QtCore.Signal(QtCore.QPoint)
|
||||
|
||||
def __init__(self, document, display_options, parent=None):
|
||||
super(ShapeEditCanvas, self).__init__(parent)
|
||||
self.setMouseTracking(True)
|
||||
|
||||
self.display_options = display_options
|
||||
method = partial(self.update_selection, False)
|
||||
self.display_options.options_changed.connect(method)
|
||||
self.display_options.options_changed.connect(self.update)
|
||||
|
||||
self.document = document
|
||||
method = partial(self.update_selection, False)
|
||||
self.document.data_changed.connect(method)
|
||||
|
||||
self.drag_shapes = []
|
||||
self.viewportmapper = ViewportMapper()
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
|
||||
self.interaction_manager = InteractionManager()
|
||||
|
||||
self.selection = Selection(self.document)
|
||||
self.selection_square = SelectionSquare()
|
||||
self.manipulator = Manipulator(self.viewportmapper)
|
||||
self.transform = Transform(load_saved_snap())
|
||||
|
||||
self.parenting_shapes = None
|
||||
self.clicked_shape = None
|
||||
self.increase_undo_on_release = False
|
||||
self.lock_background_shape = True
|
||||
|
||||
self.ctrl_pressed = False
|
||||
self.shit_pressed = False
|
||||
|
||||
def wheelEvent(self, event):
|
||||
# To center the zoom on the mouse, we save a reference mouse position
|
||||
# and compare the offset after zoom computation.
|
||||
factor = .25 if event.angleDelta().y() > 0 else -.25
|
||||
self.zoom(factor, event.pos())
|
||||
self.update()
|
||||
|
||||
def select_panel_shapes(self, panel):
|
||||
panel_shapes = [
|
||||
s for s in self.document.shapes if
|
||||
s.options['panel'] == panel]
|
||||
if panel_shapes:
|
||||
self.selection.set(panel_shapes)
|
||||
self.update_selection()
|
||||
|
||||
def focus(self):
|
||||
shapes = self.selection.shapes or self.visible_shapes()
|
||||
if not shapes:
|
||||
self.update()
|
||||
return
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
rect = get_shapes_bounding_rects(shapes)
|
||||
self.viewportmapper.focus(rect)
|
||||
self.update()
|
||||
|
||||
def zoom(self, factor, reference):
|
||||
abspoint = self.viewportmapper.to_units_coords(reference)
|
||||
if factor > 0:
|
||||
self.viewportmapper.zoomin(abs(factor))
|
||||
else:
|
||||
self.viewportmapper.zoomout(abs(factor))
|
||||
relcursor = self.viewportmapper.to_viewport_coords(abspoint)
|
||||
vector = relcursor - reference
|
||||
self.viewportmapper.origin = self.viewportmapper.origin + vector
|
||||
|
||||
def set_lock_background_shape(self, state):
|
||||
self.lock_background_shape = state
|
||||
|
||||
def get_hovered_shape(self, cursor, skip_backgrounds=False):
|
||||
for shape in reversed(self.list_shapes(skip_backgrounds)):
|
||||
if cursor_in_shape(shape, cursor):
|
||||
return shape
|
||||
|
||||
def list_shapes(self, skip_background=False):
|
||||
if self.lock_background_shape or skip_background:
|
||||
return [
|
||||
shape for shape in self.visible_shapes()
|
||||
if not shape.is_background()]
|
||||
return self.visible_shapes()
|
||||
|
||||
def leaveEvent(self, _):
|
||||
for shape in self.list_shapes():
|
||||
shape.hovered = False
|
||||
self.update()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
skip = (
|
||||
event.button() == QtCore.Qt.RightButton and
|
||||
self.interaction_manager.left_click_pressed)
|
||||
if skip:
|
||||
return
|
||||
self.setFocus(QtCore.Qt.MouseFocusReason) # This is not automatic
|
||||
if self.drag_shapes and event.button() == QtCore.Qt.LeftButton:
|
||||
pos = self.viewportmapper.to_units_coords(event.pos())
|
||||
align_shapes_on_line(self.drag_shapes, pos, pos)
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=False,
|
||||
dragging=bool(self.drag_shapes))
|
||||
self.update()
|
||||
return
|
||||
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self))
|
||||
hovered_shape = self.get_hovered_shape(cursor)
|
||||
self.transform.direction = self.manipulator.get_direction(event.pos())
|
||||
parenting = (
|
||||
self.interaction_manager.alt_pressed and not
|
||||
self.interaction_manager.ctrl_pressed and not
|
||||
self.interaction_manager.shift_pressed and
|
||||
hovered_shape and not hovered_shape.options['background'])
|
||||
|
||||
if parenting:
|
||||
self.parenting_shapes = [hovered_shape, None]
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=True,
|
||||
dragging=True)
|
||||
self.update()
|
||||
return
|
||||
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=False,
|
||||
dragging=False)
|
||||
self.update()
|
||||
return
|
||||
|
||||
conditions = (
|
||||
hovered_shape and
|
||||
hovered_shape not in self.selection and
|
||||
not self.transform.direction)
|
||||
|
||||
if conditions:
|
||||
self.selection.set([hovered_shape])
|
||||
self.update_selection()
|
||||
|
||||
elif not hovered_shape and not self.transform.direction:
|
||||
self.selection.set([])
|
||||
self.update_selection()
|
||||
self.selection_square.clicked(cursor)
|
||||
|
||||
if self.manipulator.rect is not None:
|
||||
self.transform.set_rect(self.manipulator.rect)
|
||||
self.transform.reference_rect = QtCore.QRectF(self.manipulator.rect)
|
||||
self.transform.set_reference_point(cursor)
|
||||
|
||||
self.update()
|
||||
self.interaction_manager.update(
|
||||
event,
|
||||
pressed=True,
|
||||
has_shape_hovered=bool(hovered_shape),
|
||||
dragging=bool(hovered_shape) or self.transform.direction)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
size = (event.size() - event.oldSize()) / 2
|
||||
offset = QtCore.QPointF(size.width(), size.height())
|
||||
self.viewportmapper.origin -= offset
|
||||
self.update()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self)).toPoint()
|
||||
|
||||
if self.interaction_manager.mode == InteractionManager.DRAGGING:
|
||||
if self.parenting_shapes:
|
||||
self.parenting_shapes[1] = self.get_hovered_shape(
|
||||
cursor, skip_backgrounds=True)
|
||||
self.update()
|
||||
return
|
||||
|
||||
if self.drag_shapes:
|
||||
point1 = self.viewportmapper.to_units_coords(
|
||||
self.interaction_manager.anchor)
|
||||
point2 = self.viewportmapper.to_units_coords(event.pos())
|
||||
align_shapes_on_line(self.drag_shapes, point1, point2)
|
||||
self.increase_undo_on_release = True
|
||||
return self.update()
|
||||
|
||||
rect = self.manipulator.rect
|
||||
if self.transform.direction:
|
||||
self.transform.resize(self.selection.shapes, cursor)
|
||||
elif rect is not None:
|
||||
self.transform.move(shapes=self.selection, cursor=cursor)
|
||||
for shape in self.selection:
|
||||
shape.synchronize_rect()
|
||||
shape.update_path()
|
||||
shape.synchronize_image()
|
||||
self.increase_undo_on_release = True
|
||||
self.selectedShapesChanged.emit()
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
self.selection_square.handle(cursor)
|
||||
for shape in self.list_shapes():
|
||||
shape.hovered = self.selection_square.intersects(shape)
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.NAVIGATION:
|
||||
offset = self.interaction_manager.mouse_offset(event.pos())
|
||||
if offset is not None:
|
||||
self.viewportmapper.origin = (
|
||||
self.viewportmapper.origin - offset)
|
||||
|
||||
else:
|
||||
for shape in self.list_shapes():
|
||||
shape.hovered = cursor_in_shape(shape, cursor)
|
||||
|
||||
self.update()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
return self.callContextMenu.emit(event.pos())
|
||||
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
return
|
||||
|
||||
if self.parenting_shapes:
|
||||
self.parent_shapes()
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
return
|
||||
|
||||
if self.drag_shapes:
|
||||
self.add_drag_shapes()
|
||||
|
||||
if self.increase_undo_on_release:
|
||||
self.document.record_undo()
|
||||
self.document.shapes_changed.emit()
|
||||
self.increase_undo_on_release = False
|
||||
|
||||
if self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
self.select_shapes()
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.DRAGGING:
|
||||
self.update_selection(False)
|
||||
|
||||
self.interaction_manager.update(event, pressed=False)
|
||||
self.selection_square.release()
|
||||
self.update()
|
||||
|
||||
def parent_shapes(self):
|
||||
skip = (
|
||||
not self.parenting_shapes[1] or
|
||||
self.parenting_shapes[0].options['id'] ==
|
||||
self.parenting_shapes[1].options['id'])
|
||||
if skip:
|
||||
self.parenting_shapes = None
|
||||
self.update()
|
||||
return
|
||||
|
||||
children = set(self.parenting_shapes[1].options['children'])
|
||||
children.add(self.parenting_shapes[0].options['id'])
|
||||
self.parenting_shapes[1].options['children'] = sorted(children)
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.parenting_shapes = None
|
||||
|
||||
def visible_shapes(self):
|
||||
conditions = (
|
||||
not self.display_options.isolate or
|
||||
self.display_options.current_panel < 0)
|
||||
if conditions:
|
||||
return self.document.shapes[:]
|
||||
|
||||
return [
|
||||
s for s in self.document.shapes if
|
||||
s.options['panel'] == self.display_options.current_panel]
|
||||
|
||||
def add_drag_shapes(self):
|
||||
shapes_data = [s.options for s in self.drag_shapes]
|
||||
for data in shapes_data:
|
||||
data['panel'] = max((self.display_options.current_panel, 0))
|
||||
shapes = self.document.add_shapes(shapes_data, hierarchize=True)
|
||||
self.document.shapes_changed.emit()
|
||||
self.drag_shapes = []
|
||||
self.selection.replace(shapes)
|
||||
self.update_selection()
|
||||
|
||||
def select_shapes(self):
|
||||
shapes = [
|
||||
s for s in self.list_shapes()
|
||||
if self.selection_square.intersects(s)]
|
||||
if shapes:
|
||||
self.selection.set(shapes)
|
||||
self.update_selection()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
self.key_event(event, True)
|
||||
|
||||
def keyReleaseEvent(self, event):
|
||||
self.key_event(event, False)
|
||||
|
||||
def key_event(self, event, pressed):
|
||||
if event.key() == QtCore.Qt.Key_Shift:
|
||||
self.transform.square = pressed
|
||||
self.shit_pressed = pressed
|
||||
|
||||
if event.key() == QtCore.Qt.Key_Control:
|
||||
self.ctrl_pressed = pressed
|
||||
|
||||
self.selection.mode = get_selection_mode(
|
||||
shift=self.shit_pressed,
|
||||
ctrl=self.ctrl_pressed)
|
||||
|
||||
self.update()
|
||||
|
||||
def update_selection(self, changed=True):
|
||||
shapes = [s for s in self.selection if s in self.visible_shapes()]
|
||||
if shapes:
|
||||
rect = get_shapes_bounding_rects(shapes)
|
||||
else:
|
||||
rect = None
|
||||
self.manipulator.set_rect(rect)
|
||||
if changed:
|
||||
self.selectedShapesChanged.emit()
|
||||
|
||||
def paintEvent(self, _):
|
||||
try:
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self)
|
||||
self.paint(painter)
|
||||
except BaseException:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
pass # avoid crash
|
||||
# TODO: log the error
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
def paint(self, painter):
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
visible_shapes = self.visible_shapes()
|
||||
|
||||
# Draw background and marks.
|
||||
draw_editor_canvas(
|
||||
painter, self.rect(),
|
||||
snap=self.transform.snap,
|
||||
viewportmapper=self.viewportmapper)
|
||||
|
||||
# Get the visible shapes.
|
||||
current_panel_shapes = []
|
||||
for shape in self.document.shapes:
|
||||
if shape.options['panel'] == self.display_options.current_panel:
|
||||
current_panel_shapes.append(shape)
|
||||
|
||||
# Draw the current select panel boundaries.
|
||||
if current_panel_shapes:
|
||||
rect = get_shapes_bounding_rects(current_panel_shapes)
|
||||
draw_current_panel(painter, rect, self.viewportmapper)
|
||||
|
||||
# Draw highlighted selected children.
|
||||
for id_ in self.display_options.highlighted_children_ids:
|
||||
shape = self.document.shapes_by_id.get(id_)
|
||||
if shape in visible_shapes:
|
||||
draw_shape_as_child_background(
|
||||
painter, shape, viewportmapper=self.viewportmapper)
|
||||
|
||||
if self.interaction_manager.left_click_pressed:
|
||||
visible_shapes.extend(self.drag_shapes)
|
||||
|
||||
cutter = QtGui.QPainterPath()
|
||||
cutter.setFillRule(QtCore.Qt.WindingFill)
|
||||
|
||||
for shape in visible_shapes:
|
||||
qpath = draw_shape(
|
||||
painter, shape,
|
||||
draw_selected_state=False,
|
||||
viewportmapper=self.viewportmapper)
|
||||
screen_space = shape.options['shape.space'] == 'screen'
|
||||
if not shape.options['background'] or screen_space:
|
||||
cutter.addPath(qpath)
|
||||
|
||||
connections_path = QtGui.QPainterPath()
|
||||
if self.display_options.display_hierarchy:
|
||||
for shape in visible_shapes:
|
||||
for child in shape.options['children']:
|
||||
child = self.document.shapes_by_id.get(child)
|
||||
if child is None:
|
||||
continue
|
||||
start_point = shape.bounding_rect().center()
|
||||
end_point = child.bounding_rect().center()
|
||||
path = get_connection_path(
|
||||
start_point, end_point, self.viewportmapper)
|
||||
connections_path.addPath(path)
|
||||
connections_path = connections_path.subtracted(cutter)
|
||||
draw_connections(painter, connections_path)
|
||||
|
||||
if self.parenting_shapes:
|
||||
draw_parenting_shapes(
|
||||
painter=painter,
|
||||
child=self.parenting_shapes[0],
|
||||
potential_parent=self.parenting_shapes[1],
|
||||
cursor=get_cursor(self),
|
||||
viewportmapper=self.viewportmapper)
|
||||
|
||||
conditions = (
|
||||
self.manipulator.rect is not None and
|
||||
all(self.manipulator.viewport_handlers()))
|
||||
|
||||
if conditions:
|
||||
draw_manipulator(
|
||||
painter, self.manipulator,
|
||||
get_cursor(self), self.viewportmapper)
|
||||
|
||||
if self.selection_square.rect:
|
||||
draw_selection_square(
|
||||
painter, self.selection_square.rect, self.viewportmapper)
|
||||
|
||||
def delete_selection(self):
|
||||
self.document.remove_shapes(self.selection.shapes)
|
||||
self.selection.clear()
|
||||
self.document.shapes_changed.emit()
|
||||
self.manipulator.set_rect(None)
|
||||
self.document.record_undo()
|
||||
17
2023/scripts/animation_tools/dwpicker/designer/display.py
Normal file
17
2023/scripts/animation_tools/dwpicker/designer/display.py
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
from ..pyside import QtCore
|
||||
from maya import cmds
|
||||
from ..optionvar import (
|
||||
ISOLATE_CURRENT_PANEL_SHAPES, DISPLAY_HIERARCHY_IN_CANVAS)
|
||||
|
||||
|
||||
class DisplayOptions(QtCore.QObject):
|
||||
options_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self):
|
||||
super(DisplayOptions, self).__init__()
|
||||
self.isolate = cmds.optionVar(query=ISOLATE_CURRENT_PANEL_SHAPES)
|
||||
self.current_panel = -1
|
||||
self.highlighted_children_ids = []
|
||||
state = cmds.optionVar(query=DISPLAY_HIERARCHY_IN_CANVAS)
|
||||
self.display_hierarchy = bool(state)
|
||||
613
2023/scripts/animation_tools/dwpicker/designer/editor.py
Normal file
613
2023/scripts/animation_tools/dwpicker/designer/editor.py
Normal file
@@ -0,0 +1,613 @@
|
||||
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()
|
||||
114
2023/scripts/animation_tools/dwpicker/designer/highlighter.py
Normal file
114
2023/scripts/animation_tools/dwpicker/designer/highlighter.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import keyword
|
||||
from ..pyside import QtGui, QtCore
|
||||
from ..languages import PYTHON, MEL
|
||||
|
||||
|
||||
MELKEYWORDS = [
|
||||
'if', 'else', 'int', 'float', 'double', 'string', 'array'
|
||||
'var', 'return', 'case', 'then', 'continue', 'break', 'global', 'proc']
|
||||
|
||||
|
||||
TEXT_STYLES = {
|
||||
'keyword': {
|
||||
'color': 'white',
|
||||
'bold': True,
|
||||
'italic': False},
|
||||
'number': {
|
||||
'color': 'cyan',
|
||||
'bold': False,
|
||||
'italic': False},
|
||||
'comment': {
|
||||
'color': (0.7, 0.5, 0.5),
|
||||
'bold': False,
|
||||
'italic': False},
|
||||
'function': {
|
||||
'color': '#ff0571',
|
||||
'bold': False,
|
||||
'italic': True},
|
||||
'string': {
|
||||
'color': 'yellow',
|
||||
'bold': False,
|
||||
'italic': False},
|
||||
'boolean': {
|
||||
'color': '#a18852',
|
||||
'bold': True,
|
||||
'italic': False}}
|
||||
|
||||
|
||||
PATTERNS = {
|
||||
PYTHON: {
|
||||
'keyword': r'\b|'.join(keyword.kwlist),
|
||||
'number': r'\b[+-]?[0-9]+[lL]?\b',
|
||||
'comment': r'#[^\n]*',
|
||||
'function': r'\b[A-Za-z0-9_]+(?=\()',
|
||||
'string': r'".*"|\'.*\'',
|
||||
'boolean': r'\bTrue\b|\bFalse\b'},
|
||||
MEL: {
|
||||
'keyword': r'\b|'.join(MELKEYWORDS),
|
||||
'number': r'\b[+-]?[0-9]+[lL]?\b',
|
||||
'comment': r'//[^\n]*',
|
||||
'function': r'\b[A-Za-z0-9_]+(?=\()',
|
||||
'string': r'".*"|\'.*\'',
|
||||
'boolean': r'\btrue\b|\bfalse\b'}
|
||||
}
|
||||
|
||||
|
||||
class Highlighter(QtGui.QSyntaxHighlighter):
|
||||
PATTERNS = []
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Highlighter, self).__init__(parent)
|
||||
self.rules = []
|
||||
for name, properties in TEXT_STYLES.items():
|
||||
if name not in self.PATTERNS:
|
||||
continue
|
||||
text_format = create_textcharformat(
|
||||
color=properties['color'],
|
||||
bold=properties['bold'],
|
||||
italic=properties['italic'])
|
||||
|
||||
rule = QtCore.QRegularExpression(self.PATTERNS[name]), text_format
|
||||
self.rules.append(rule)
|
||||
|
||||
def highlightBlock(self, text):
|
||||
for pattern, format_ in self.rules:
|
||||
expression = QtCore.QRegularExpression(pattern)
|
||||
iterator = expression.globalMatch(text)
|
||||
while iterator.hasNext():
|
||||
match = iterator.next()
|
||||
index = match.capturedStart()
|
||||
length = match.capturedLength()
|
||||
self.setFormat(index, length, format_)
|
||||
|
||||
|
||||
class PythonHighlighter(Highlighter):
|
||||
PATTERNS = PATTERNS[PYTHON]
|
||||
|
||||
|
||||
class MelHighlighter(Highlighter):
|
||||
PATTERNS = PATTERNS[MEL]
|
||||
|
||||
|
||||
HIGHLIGHTERS = {
|
||||
PYTHON: PythonHighlighter,
|
||||
MEL: MelHighlighter}
|
||||
|
||||
|
||||
def get_highlighter(language):
|
||||
return HIGHLIGHTERS.get(language, Highlighter)
|
||||
|
||||
|
||||
def create_textcharformat(color, bold=False, italic=False):
|
||||
char_format = QtGui.QTextCharFormat()
|
||||
qcolor = QtGui.QColor()
|
||||
if isinstance(color, str):
|
||||
qcolor.setNamedColor(color)
|
||||
else:
|
||||
r, g, b = color
|
||||
qcolor.setRgbF(r, g, b)
|
||||
char_format.setForeground(qcolor)
|
||||
if bold:
|
||||
char_format.setFontWeight(QtGui.QFont.Bold)
|
||||
if italic:
|
||||
char_format.setFontItalic(True)
|
||||
return char_format
|
||||
162
2023/scripts/animation_tools/dwpicker/designer/layer.py
Normal file
162
2023/scripts/animation_tools/dwpicker/designer/layer.py
Normal file
@@ -0,0 +1,162 @@
|
||||
from functools import partial
|
||||
from ..pyside import QtWidgets, QtCore, QtGui
|
||||
from ..widgets import V, CheckWidget
|
||||
|
||||
|
||||
class VisibilityLayersEditor(QtWidgets.QWidget):
|
||||
selectLayerContent = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, document, parent=None):
|
||||
super(VisibilityLayersEditor, self).__init__(parent)
|
||||
self.document = document
|
||||
|
||||
self.model = VisbilityLayersModel(document)
|
||||
|
||||
self.table = QtWidgets.QTableView()
|
||||
self.table.setModel(self.model)
|
||||
self.table.setItemDelegateForColumn(0, CheckDelegate())
|
||||
self.table.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
|
||||
self.table.verticalHeader().hide()
|
||||
self.table.horizontalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.table.verticalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||
self.table.setFixedHeight(100)
|
||||
|
||||
self.select_content = QtWidgets.QPushButton('Select layer content')
|
||||
self.select_content.released.connect(self.call_select_layer)
|
||||
self.remove_layer = QtWidgets.QPushButton('Remove selected layer')
|
||||
self.remove_layer.released.connect(self.call_remove_layer)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(self.table)
|
||||
layout.addWidget(self.select_content)
|
||||
layout.addWidget(self.remove_layer)
|
||||
|
||||
def selected_layer(self):
|
||||
indexes = self.table.selectedIndexes()
|
||||
if not indexes:
|
||||
return
|
||||
|
||||
layer = sorted(list(self.document.shapes_by_layer))[indexes[0].row()]
|
||||
return layer
|
||||
|
||||
def call_remove_layer(self):
|
||||
layer = self.selected_layer()
|
||||
if not layer:
|
||||
return
|
||||
|
||||
for shape in self.document.shapes_by_layer[layer]:
|
||||
if shape.visibility_layer() == layer:
|
||||
shape.options['visibility_layer'] = None
|
||||
self.model.layoutAboutToBeChanged.emit()
|
||||
self.document.sync_shapes_caches()
|
||||
self.document.shapes_changed.emit()
|
||||
self.document.record_undo()
|
||||
self.model.layoutChanged.emit()
|
||||
|
||||
def call_select_layer(self):
|
||||
layer = self.selected_layer()
|
||||
if not layer:
|
||||
return
|
||||
self.selectLayerContent.emit(layer)
|
||||
|
||||
|
||||
class CheckDelegate(QtWidgets.QItemDelegate):
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
self.update()
|
||||
|
||||
def createEditor(self, parent, _, index):
|
||||
model = index.model()
|
||||
hidden_layers = model.document.data['general']['hidden_layers']
|
||||
layer = model.data(index)
|
||||
state = layer in hidden_layers
|
||||
model.set_hidden_layer(layer, not state)
|
||||
|
||||
checker = CheckWidget(not state, parent)
|
||||
checker.toggled.connect(partial(model.set_hidden_layer, layer))
|
||||
return checker
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
model = index.model()
|
||||
hidden_layers = model.document.data['general']['hidden_layers']
|
||||
state = model.data(index) in hidden_layers
|
||||
|
||||
center = option.rect.center()
|
||||
painter.setBrush(QtCore.Qt.NoBrush)
|
||||
rect = QtCore.QRectF(center.x() - 10, center.y() - 10, 20, 20)
|
||||
if not state:
|
||||
return
|
||||
font = QtGui.QFont()
|
||||
font.setPixelSize(20)
|
||||
option = QtGui.QTextOption()
|
||||
option.setAlignment(QtCore.Qt.AlignCenter)
|
||||
painter.drawText(rect, V, option)
|
||||
|
||||
|
||||
class VisbilityLayersModel(QtCore.QAbstractTableModel):
|
||||
HEADERS = 'hide', 'name', 'shapes'
|
||||
|
||||
def __init__(self, document, parent=None):
|
||||
super(VisbilityLayersModel, self).__init__(parent)
|
||||
self.document = document
|
||||
self.document.changed.connect(self.layoutChanged.emit)
|
||||
|
||||
def rowCount(self, _):
|
||||
return len(self.document.shapes_by_layer)
|
||||
|
||||
def columnCount(self, _):
|
||||
return len(self.HEADERS)
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation == QtCore.Qt.Vertical or role != QtCore.Qt.DisplayRole:
|
||||
return
|
||||
return self.HEADERS[section]
|
||||
|
||||
def flags(self, index):
|
||||
flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||
if index.column() == 0:
|
||||
flags |= QtCore.Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
def set_hidden_layer(self, layer, state):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
hidden_layers = self.document.data['general']['hidden_layers']
|
||||
if state and layer not in hidden_layers:
|
||||
hidden_layers.append(layer)
|
||||
elif not state and layer in hidden_layers:
|
||||
hidden_layers.remove(layer)
|
||||
else:
|
||||
self.layoutChanged.emit()
|
||||
return
|
||||
self.document.record_undo()
|
||||
self.document.general_option_changed.emit('editor', 'hidden_layers')
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def data(self, index, role=QtCore.Qt.UserRole):
|
||||
if not index.isValid():
|
||||
return
|
||||
if role == QtCore.Qt.TextAlignmentRole:
|
||||
if index.column() == 2:
|
||||
return QtCore.Qt.AlignCenter
|
||||
|
||||
layers = sorted(list(self.document.shapes_by_layer))
|
||||
|
||||
if role == QtCore.Qt.UserRole:
|
||||
return layers[index.row()]
|
||||
|
||||
if role != QtCore.Qt.DisplayRole:
|
||||
return
|
||||
|
||||
if index.column() == 1:
|
||||
return layers[index.row()]
|
||||
|
||||
if index.column() == 2:
|
||||
layer = layers[index.row()]
|
||||
return str(len(self.document.shapes_by_layer[layer]))
|
||||
289
2023/scripts/animation_tools/dwpicker/designer/menu.py
Normal file
289
2023/scripts/animation_tools/dwpicker/designer/menu.py
Normal file
@@ -0,0 +1,289 @@
|
||||
from functools import partial
|
||||
from maya import cmds
|
||||
from ..pyside import QtGui, QtWidgets, QtCore
|
||||
|
||||
from ..optionvar import (
|
||||
BG_LOCKED, DISPLAY_HIERARCHY_IN_CANVAS, ISOLATE_CURRENT_PANEL_SHAPES,
|
||||
SNAP_ITEMS, SNAP_GRID_X, SNAP_GRID_Y, save_optionvar)
|
||||
from ..qtutils import icon
|
||||
|
||||
|
||||
class MenuWidget(QtWidgets.QWidget):
|
||||
addBackgroundRequested = QtCore.Signal()
|
||||
addButtonRequested = QtCore.Signal()
|
||||
addTextRequested = QtCore.Signal()
|
||||
alignRequested = QtCore.Signal(str)
|
||||
arrangeRequested = QtCore.Signal(str)
|
||||
buttonLibraryRequested = QtCore.Signal(QtCore.QPoint)
|
||||
centerValuesChanged = QtCore.Signal(int, int)
|
||||
copyRequested = QtCore.Signal()
|
||||
copySettingsRequested = QtCore.Signal()
|
||||
deleteRequested = QtCore.Signal()
|
||||
editCenterToggled = QtCore.Signal(bool)
|
||||
lockBackgroundShapeToggled = QtCore.Signal(bool)
|
||||
moveDownRequested = QtCore.Signal()
|
||||
moveUpRequested = QtCore.Signal()
|
||||
onBottomRequested = QtCore.Signal()
|
||||
onTopRequested = QtCore.Signal()
|
||||
pasteRequested = QtCore.Signal()
|
||||
pasteSettingsRequested = QtCore.Signal()
|
||||
redoRequested = QtCore.Signal()
|
||||
searchAndReplaceRequested = QtCore.Signal()
|
||||
snapValuesChanged = QtCore.Signal()
|
||||
symmetryRequested = QtCore.Signal(bool)
|
||||
undoRequested = QtCore.Signal()
|
||||
useSnapToggled = QtCore.Signal(bool)
|
||||
|
||||
def __init__(self, display_options, parent=None):
|
||||
super(MenuWidget, self).__init__(parent=parent)
|
||||
self.display_options = display_options
|
||||
|
||||
self.delete = QtWidgets.QAction(icon('delete.png'), '', self)
|
||||
self.delete.setToolTip('Delete selection')
|
||||
self.delete.triggered.connect(self.deleteRequested.emit)
|
||||
|
||||
self.copy = QtWidgets.QAction(icon('copy.png'), '', self)
|
||||
self.copy.setToolTip('Copy selection')
|
||||
self.copy.triggered.connect(self.copyRequested.emit)
|
||||
|
||||
self.paste = QtWidgets.QAction(icon('paste.png'), '', self)
|
||||
self.paste.setToolTip('Paste')
|
||||
self.paste.triggered.connect(self.pasteRequested.emit)
|
||||
|
||||
self.undo = QtWidgets.QAction(icon('undo.png'), '', self)
|
||||
self.undo.setToolTip('Undo')
|
||||
self.undo.triggered.connect(self.undoRequested.emit)
|
||||
self.redo = QtWidgets.QAction(icon('redo.png'), '', self)
|
||||
self.redo.setToolTip('Redo')
|
||||
self.redo.triggered.connect(self.redoRequested.emit)
|
||||
|
||||
icon_ = icon('copy_settings.png')
|
||||
self.copy_settings = QtWidgets.QAction(icon_, '', self)
|
||||
self.copy_settings.setToolTip('Copy settings')
|
||||
self.copy_settings.triggered.connect(self.copySettingsRequested.emit)
|
||||
icon_ = icon('paste_settings.png')
|
||||
self.paste_settings = QtWidgets.QAction(icon_, '', self)
|
||||
self.paste_settings.setToolTip('Paste settings')
|
||||
self.paste_settings.triggered.connect(self.pasteSettingsRequested.emit)
|
||||
|
||||
self.search = QtWidgets.QAction(icon('search.png'), '', self)
|
||||
self.search.triggered.connect(self.searchAndReplaceRequested.emit)
|
||||
self.search.setToolTip('Search and replace')
|
||||
|
||||
icon_ = icon('lock-non-interactive.png')
|
||||
self.lock_bg = QtWidgets.QAction(icon_, '', self)
|
||||
self.lock_bg.setToolTip('Lock background items')
|
||||
self.lock_bg.setCheckable(True)
|
||||
self.lock_bg.triggered.connect(self.save_ui_states)
|
||||
self.lock_bg.toggled.connect(self.lockBackgroundShapeToggled.emit)
|
||||
|
||||
self.isolate = QtWidgets.QAction(icon('isolate.png'), '', self)
|
||||
self.isolate.setToolTip('Isolate current panel shapes')
|
||||
self.isolate.setCheckable(True)
|
||||
self.isolate.toggled.connect(self.isolate_panel)
|
||||
|
||||
self.hierarchy = QtWidgets.QAction(icon('hierarchy.png'), '', self)
|
||||
self.hierarchy.setToolTip('Display hierarchy')
|
||||
self.hierarchy.setCheckable(True)
|
||||
state = bool(cmds.optionVar(query=DISPLAY_HIERARCHY_IN_CANVAS))
|
||||
self.hierarchy.setChecked(state)
|
||||
self.hierarchy.toggled.connect(self.toggle_hierarchy_display)
|
||||
|
||||
self.snap = QtWidgets.QAction(icon('snap.png'), '', self)
|
||||
self.snap.setToolTip('Snap grid enable')
|
||||
self.snap.setCheckable(True)
|
||||
self.snap.triggered.connect(self.snap_toggled)
|
||||
validator = QtGui.QIntValidator(5, 150)
|
||||
self.snapx = QtWidgets.QLineEdit('10')
|
||||
self.snapx.setFixedWidth(35)
|
||||
self.snapx.setValidator(validator)
|
||||
self.snapx.setEnabled(False)
|
||||
self.snapx.textEdited.connect(self.snap_value_changed)
|
||||
self.snapy = QtWidgets.QLineEdit('10')
|
||||
self.snapy.setFixedWidth(35)
|
||||
self.snapy.setValidator(validator)
|
||||
self.snapy.setEnabled(False)
|
||||
self.snapy.textEdited.connect(self.snap_value_changed)
|
||||
self.snap.toggled.connect(self.snapx.setEnabled)
|
||||
self.snap.toggled.connect(self.snapy.setEnabled)
|
||||
|
||||
icon_ = icon('addshape.png')
|
||||
self.call_library = QtWidgets.QAction(icon_, '', self)
|
||||
self.call_library.setToolTip('Add button')
|
||||
self.call_library.triggered.connect(self._call_library)
|
||||
icon_ = icon('addbutton.png')
|
||||
self.addbutton = QtWidgets.QAction(icon_, '', self)
|
||||
self.addbutton.setToolTip('Add button')
|
||||
self.addbutton.triggered.connect(self.addButtonRequested.emit)
|
||||
self.addtext = QtWidgets.QAction(icon('addtext.png'), '', self)
|
||||
self.addtext.setToolTip('Add text')
|
||||
self.addtext.triggered.connect(self.addTextRequested.emit)
|
||||
self.addbg = QtWidgets.QAction(icon('addbg.png'), '', self)
|
||||
self.addbg.setToolTip('Add background shape')
|
||||
self.addbg.triggered.connect(self.addBackgroundRequested.emit)
|
||||
|
||||
icon_ = icon('onbottom.png')
|
||||
self.onbottom = QtWidgets.QAction(icon_, '', self)
|
||||
self.onbottom.setToolTip('Set selected shapes on bottom')
|
||||
self.onbottom.triggered.connect(self.onBottomRequested.emit)
|
||||
icon_ = icon('movedown.png')
|
||||
self.movedown = QtWidgets.QAction(icon_, '', self)
|
||||
self.movedown.setToolTip('Move down selected shapes')
|
||||
self.movedown.triggered.connect(self.moveDownRequested.emit)
|
||||
self.moveup = QtWidgets.QAction(icon('moveup.png'), '', self)
|
||||
self.moveup.setToolTip('Move up selected shapes')
|
||||
self.moveup.triggered.connect(self.moveUpRequested.emit)
|
||||
self.ontop = QtWidgets.QAction(icon('ontop.png'), '', self)
|
||||
self.ontop.setToolTip('Set selected shapes on top')
|
||||
self.ontop.triggered.connect(self.onTopRequested.emit)
|
||||
|
||||
self.hsymmetry = QtWidgets.QAction(icon('h_symmetry.png'), '', self)
|
||||
self.hsymmetry.setToolTip('Mirror a shape horizontally')
|
||||
method = partial(self.symmetryRequested.emit, True)
|
||||
self.hsymmetry.triggered.connect(method)
|
||||
self.vsymmetry = QtWidgets.QAction(icon('v_symmetry.png'), '', self)
|
||||
self.vsymmetry.setToolTip('Mirror a shape vertically')
|
||||
method = partial(self.symmetryRequested.emit, False)
|
||||
self.vsymmetry.triggered.connect(method)
|
||||
|
||||
method = partial(self.alignRequested.emit, 'left')
|
||||
self.align_left = QtWidgets.QAction(icon('align_left.png'), '', self)
|
||||
self.align_left.triggered.connect(method)
|
||||
self.align_left.setToolTip('Align to left')
|
||||
file_ = 'align_h_center.png'
|
||||
method = partial(self.alignRequested.emit, 'h_center')
|
||||
self.align_h_center = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.align_h_center.triggered.connect(method)
|
||||
self.align_h_center.setToolTip('Align to center horizontally')
|
||||
method = partial(self.alignRequested.emit, 'right')
|
||||
self.align_right = QtWidgets.QAction(icon('align_right.png'), '', self)
|
||||
self.align_right.triggered.connect(method)
|
||||
self.align_right.setToolTip('Align to right')
|
||||
method = partial(self.alignRequested.emit, 'top')
|
||||
self.align_top = QtWidgets.QAction(icon('align_top.png'), '', self)
|
||||
self.align_top.triggered.connect(method)
|
||||
self.align_top.setToolTip('Align to top')
|
||||
file_ = 'align_v_center.png'
|
||||
self.align_v_center = QtWidgets.QAction(icon(file_), '', self)
|
||||
method = partial(self.alignRequested.emit, 'v_center')
|
||||
self.align_v_center.triggered.connect(method)
|
||||
self.align_v_center.setToolTip('Align to center vertically')
|
||||
file_ = 'align_bottom.png'
|
||||
method = partial(self.alignRequested.emit, 'bottom')
|
||||
self.align_bottom = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.align_bottom.triggered.connect(method)
|
||||
self.align_bottom.setToolTip('Align to bottom')
|
||||
|
||||
file_ = 'arrange_h.png'
|
||||
method = partial(self.arrangeRequested.emit, 'horizontal')
|
||||
self.arrange_horizontal = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.arrange_horizontal.triggered.connect(method)
|
||||
self.arrange_horizontal.setToolTip('Distribute horizontally')
|
||||
|
||||
file_ = 'arrange_v.png'
|
||||
method = partial(self.arrangeRequested.emit, 'vertical')
|
||||
self.arrange_vertical = QtWidgets.QAction(icon(file_), '', self)
|
||||
self.arrange_vertical.triggered.connect(method)
|
||||
self.arrange_vertical.setToolTip('Distribute vertically')
|
||||
|
||||
self.toolbar = QtWidgets.QToolBar()
|
||||
self.toolbar.setIconSize(QtCore.QSize(24, 24))
|
||||
self.toolbar.addAction(self.delete)
|
||||
self.toolbar.addAction(self.copy)
|
||||
self.toolbar.addAction(self.paste)
|
||||
self.toolbar.addAction(self.copy_settings)
|
||||
self.toolbar.addAction(self.paste_settings)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.undo)
|
||||
self.toolbar.addAction(self.redo)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.search)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.lock_bg)
|
||||
self.toolbar.addAction(self.isolate)
|
||||
self.toolbar.addAction(self.hierarchy)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.snap)
|
||||
self.toolbar.addWidget(self.snapx)
|
||||
self.toolbar.addWidget(self.snapy)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.call_library)
|
||||
self.toolbar.addAction(self.addbutton)
|
||||
self.toolbar.addAction(self.addtext)
|
||||
self.toolbar.addAction(self.addbg)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.hsymmetry)
|
||||
self.toolbar.addAction(self.vsymmetry)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.onbottom)
|
||||
self.toolbar.addAction(self.movedown)
|
||||
self.toolbar.addAction(self.moveup)
|
||||
self.toolbar.addAction(self.ontop)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(self.align_left)
|
||||
self.toolbar.addAction(self.align_h_center)
|
||||
self.toolbar.addAction(self.align_right)
|
||||
self.toolbar.addAction(self.align_top)
|
||||
self.toolbar.addAction(self.align_v_center)
|
||||
self.toolbar.addAction(self.align_bottom)
|
||||
self.toolbar.addAction(self.arrange_horizontal)
|
||||
self.toolbar.addAction(self.arrange_vertical)
|
||||
|
||||
self.layout = QtWidgets.QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0, 0, 10, 0)
|
||||
self.layout.addWidget(self.toolbar)
|
||||
|
||||
self.load_ui_states()
|
||||
|
||||
def toggle_hierarchy_display(self, state):
|
||||
save_optionvar(DISPLAY_HIERARCHY_IN_CANVAS, int(state))
|
||||
self.display_options.display_hierarchy = state
|
||||
self.display_options.options_changed.emit()
|
||||
|
||||
def isolate_panel(self, state):
|
||||
self.display_options.isolate = state
|
||||
self.display_options.options_changed.emit()
|
||||
|
||||
def _call_library(self):
|
||||
rect = self.toolbar.actionGeometry(self.call_library)
|
||||
point = self.toolbar.mapToGlobal(rect.bottomLeft())
|
||||
self.buttonLibraryRequested.emit(point)
|
||||
|
||||
def load_ui_states(self):
|
||||
self.snap.setChecked(cmds.optionVar(query=SNAP_ITEMS))
|
||||
value = str(cmds.optionVar(query=SNAP_GRID_X))
|
||||
self.snapx.setText(value)
|
||||
value = str(cmds.optionVar(query=SNAP_GRID_Y))
|
||||
self.snapy.setText(value)
|
||||
self.lock_bg.setChecked(bool(cmds.optionVar(query=BG_LOCKED)))
|
||||
value = bool(cmds.optionVar(query=ISOLATE_CURRENT_PANEL_SHAPES))
|
||||
self.isolate.setChecked(value)
|
||||
|
||||
def save_ui_states(self):
|
||||
save_optionvar(BG_LOCKED, int(self.lock_bg.isChecked()))
|
||||
save_optionvar(SNAP_ITEMS, int(self.snap.isChecked()))
|
||||
save_optionvar(SNAP_GRID_X, int(self.snapx.text()))
|
||||
save_optionvar(SNAP_GRID_Y, int(self.snapy.text()))
|
||||
value = int(self.isolate.isChecked())
|
||||
save_optionvar(ISOLATE_CURRENT_PANEL_SHAPES, value)
|
||||
|
||||
def size_changed(self, *_):
|
||||
self.sizeChanged.emit()
|
||||
|
||||
def edit_center_toggled(self):
|
||||
self.editCenterToggled.emit(self.editcenter.isChecked())
|
||||
|
||||
def snap_toggled(self):
|
||||
self.useSnapToggled.emit(self.snap.isChecked())
|
||||
self.save_ui_states()
|
||||
|
||||
def snap_values(self):
|
||||
x = int(self.snapx.text()) if self.snapx.text() else 1
|
||||
y = int(self.snapy.text()) if self.snapy.text() else 1
|
||||
x = x if x > 0 else 1
|
||||
y = y if y > 0 else 1
|
||||
return x, y
|
||||
|
||||
def snap_value_changed(self, _):
|
||||
self.snapValuesChanged.emit()
|
||||
self.save_ui_states()
|
||||
703
2023/scripts/animation_tools/dwpicker/designer/patheditor.py
Normal file
703
2023/scripts/animation_tools/dwpicker/designer/patheditor.py
Normal file
@@ -0,0 +1,703 @@
|
||||
import os
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
|
||||
from ..pyside import QtWidgets, QtCore, QtGui
|
||||
from maya import cmds
|
||||
|
||||
from ..geometry import (
|
||||
distance, get_global_rect, grow_rect, path_symmetry)
|
||||
from ..qtutils import icon
|
||||
from ..interactionmanager import InteractionManager
|
||||
from ..interactive import SelectionSquare, Manipulator
|
||||
from ..optionvar import (
|
||||
LAST_OPEN_DIRECTORY, SHAPE_PATH_ROTATION_STEP_ANGLE, save_optionvar)
|
||||
from ..painting import (
|
||||
draw_selection_square, draw_manipulator, draw_tangents,
|
||||
draw_world_coordinates)
|
||||
from ..path import get_open_directory
|
||||
from ..qtutils import get_cursor
|
||||
from ..selection import get_selection_mode
|
||||
from ..shapepath import (
|
||||
offset_tangent, offset_path, auto_tangent, create_polygon_path,
|
||||
rotate_path)
|
||||
from ..transform import (
|
||||
Transform, resize_path_with_reference, resize_rect_with_direction)
|
||||
from ..viewport import ViewportMapper
|
||||
|
||||
|
||||
class PathEditor(QtWidgets.QWidget):
|
||||
pathEdited = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(PathEditor, self).__init__(parent)
|
||||
self.setWindowTitle('Shape path editor')
|
||||
self.buffer_path = None
|
||||
self.rotate_center = None
|
||||
|
||||
self.canvas = PathEditorCanvas()
|
||||
self.canvas.pathEdited.connect(self.pathEdited.emit)
|
||||
|
||||
export_path = QtWidgets.QAction(icon('save.png'), 'Export path', self)
|
||||
export_path.triggered.connect(self.export_path)
|
||||
|
||||
import_path = QtWidgets.QAction(icon('open.png'), 'Import path', self)
|
||||
import_path.triggered.connect(self.import_path)
|
||||
|
||||
delete = QtWidgets.QAction(icon('delete.png'), 'Delete vertex', self)
|
||||
delete.triggered.connect(self.canvas.delete)
|
||||
|
||||
smooth_tangent = QtWidgets.QAction(
|
||||
icon('tangent.png'), 'Smooth tangents', self)
|
||||
smooth_tangent.triggered.connect(self.canvas.smooth_tangents)
|
||||
|
||||
break_tangent = QtWidgets.QAction(
|
||||
icon('tangentbreak.png'), 'Break tangents', self)
|
||||
break_tangent.triggered.connect(self.canvas.break_tangents)
|
||||
|
||||
hsymmetry = QtWidgets.QAction(
|
||||
icon('h_symmetry.png'), 'Mirror horizontally', self)
|
||||
hsymmetry.triggered.connect(partial(self.canvas.symmetry, True))
|
||||
|
||||
vsymmetry = QtWidgets.QAction(
|
||||
icon('v_symmetry.png'), 'Mirror vertically', self)
|
||||
vsymmetry.triggered.connect(partial(self.canvas.symmetry, False))
|
||||
|
||||
center_path = QtWidgets.QAction(
|
||||
icon('frame.png'), 'Center path', self)
|
||||
center_path.triggered.connect(partial(self.canvas.center_path))
|
||||
center_path_button = QtWidgets.QToolButton()
|
||||
center_path_button.setDefaultAction(center_path)
|
||||
|
||||
self.angle = QtWidgets.QSlider(QtCore.Qt.Horizontal)
|
||||
self.angle.setMinimum(0)
|
||||
self.angle.setMaximum(360)
|
||||
self.angle.sliderPressed.connect(self.start_rotate)
|
||||
self.angle.sliderReleased.connect(self.end_rotate)
|
||||
self.angle.valueChanged.connect(self.rotate)
|
||||
|
||||
self.angle_step = QtWidgets.QSpinBox()
|
||||
self.angle_step.setToolTip('Step')
|
||||
self.angle_step.setMinimum(0)
|
||||
self.angle_step.setMaximum(90)
|
||||
value = cmds.optionVar(query=SHAPE_PATH_ROTATION_STEP_ANGLE)
|
||||
self.angle_step.setValue(value)
|
||||
function = partial(save_optionvar, SHAPE_PATH_ROTATION_STEP_ANGLE)
|
||||
self.angle_step.valueChanged.connect(function)
|
||||
|
||||
polygon = QtWidgets.QAction(
|
||||
icon('polygon.png'), 'Create Polygon', self)
|
||||
polygon.triggered.connect(self.create_polygon)
|
||||
|
||||
toggle = QtWidgets.QAction(icon('dock.png'), 'Dock/Undock', self)
|
||||
toggle.triggered.connect(self.toggle_flag)
|
||||
|
||||
self.toolbar = QtWidgets.QToolBar()
|
||||
self.toolbar.setIconSize(QtCore.QSize(18, 18))
|
||||
self.toolbar.addAction(import_path)
|
||||
self.toolbar.addAction(export_path)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(polygon)
|
||||
self.toolbar.addAction(delete)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(smooth_tangent)
|
||||
self.toolbar.addAction(break_tangent)
|
||||
self.toolbar.addSeparator()
|
||||
self.toolbar.addAction(hsymmetry)
|
||||
self.toolbar.addAction(vsymmetry)
|
||||
|
||||
toolbar3 = QtWidgets.QHBoxLayout()
|
||||
toolbar3.setContentsMargins(0, 0, 0, 0)
|
||||
toolbar3.addWidget(center_path_button)
|
||||
toolbar3.addStretch()
|
||||
toolbar3.addWidget(QtWidgets.QLabel('Rotate: '))
|
||||
toolbar3.addWidget(self.angle)
|
||||
toolbar3.addWidget(self.angle_step)
|
||||
|
||||
self.toolbar2 = QtWidgets.QToolBar()
|
||||
self.toolbar2.setIconSize(QtCore.QSize(18, 18))
|
||||
self.toolbar2.addAction(toggle)
|
||||
|
||||
toolbars = QtWidgets.QHBoxLayout()
|
||||
toolbars.setContentsMargins(0, 0, 0, 0)
|
||||
toolbars.addWidget(self.toolbar)
|
||||
toolbars.addStretch()
|
||||
toolbars.addWidget(self.toolbar2)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addLayout(toolbars)
|
||||
layout.addWidget(self.canvas)
|
||||
layout.addLayout(toolbar3)
|
||||
|
||||
def export_path(self):
|
||||
directory = get_open_directory()
|
||||
filename = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self, 'Export shape', directory, '*.dws')
|
||||
if not filename[0]:
|
||||
return
|
||||
save_optionvar(LAST_OPEN_DIRECTORY, os.path.dirname(filename[0]))
|
||||
with open(filename[0], 'w') as f:
|
||||
json.dump(self.canvas.path, f, indent=2)
|
||||
|
||||
def import_path(self):
|
||||
directory = get_open_directory()
|
||||
filename = QtWidgets.QFileDialog.getOpenFileName(
|
||||
self, 'Import shape', directory, '*.dws')
|
||||
if not filename[0]:
|
||||
return
|
||||
save_optionvar(LAST_OPEN_DIRECTORY, os.path.dirname(filename[0]))
|
||||
with open(filename[0], 'r') as f:
|
||||
path = json.load(f)
|
||||
self.canvas.set_path(path)
|
||||
self.pathEdited.emit()
|
||||
|
||||
def start_rotate(self):
|
||||
self.buffer_path = deepcopy(self.canvas.path)
|
||||
if not self.canvas.selection:
|
||||
self.rotate_center = (0, 0)
|
||||
elif len(self.canvas.selection) == 1:
|
||||
index = self.canvas.selection[0]
|
||||
self.rotate_center = self.buffer_path[index]['point']
|
||||
else:
|
||||
point = self.canvas.manipulator.rect.center()
|
||||
self.rotate_center = point.toTuple()
|
||||
|
||||
def end_rotate(self):
|
||||
self.buffer_path = None
|
||||
self.rotate_center = None
|
||||
self.pathEdited.emit()
|
||||
self.angle.blockSignals(True)
|
||||
self.angle.setValue(0)
|
||||
self.angle.blockSignals(False)
|
||||
|
||||
def rotate(self, value):
|
||||
if self.buffer_path is None:
|
||||
self.start_rotate()
|
||||
|
||||
step_size = self.angle_step.value()
|
||||
value = round(value / step_size) * step_size
|
||||
if 1 < len(self.canvas.selection):
|
||||
path = deepcopy(self.buffer_path)
|
||||
points = [self.buffer_path[i] for i in self.canvas.selection]
|
||||
rotated_path = rotate_path(points, value, self.rotate_center)
|
||||
for i, point in zip(self.canvas.selection, rotated_path):
|
||||
path[i] = point
|
||||
else:
|
||||
path = rotate_path(self.buffer_path, value, self.rotate_center)
|
||||
if path is None:
|
||||
return
|
||||
|
||||
self.canvas.path = path
|
||||
if self.canvas.selection:
|
||||
points = [
|
||||
QtCore.QPointF(*path[i]['point'])
|
||||
for i in self.canvas.selection]
|
||||
self.canvas.update_manipulator_rect(points)
|
||||
self.canvas.update()
|
||||
|
||||
def create_polygon(self):
|
||||
edges, result = QtWidgets.QInputDialog.getInt(
|
||||
self, 'Polygon', 'Number of edges', value=3, minValue=3,
|
||||
maxValue=25)
|
||||
if not result:
|
||||
return
|
||||
|
||||
path = create_polygon_path(radius=45, n=edges)
|
||||
self.canvas.set_path(path)
|
||||
self.pathEdited.emit()
|
||||
|
||||
def toggle_flag(self):
|
||||
point = self.mapToGlobal(self.rect().topLeft())
|
||||
state = not self.windowFlags() & QtCore.Qt.Tool
|
||||
self.setWindowFlag(QtCore.Qt.Tool, state)
|
||||
self.show()
|
||||
if state:
|
||||
self.resize(400, 400)
|
||||
self.move(point)
|
||||
self.canvas.focus()
|
||||
|
||||
def set_options(self, options):
|
||||
if options is None:
|
||||
self.canvas.set_path(None)
|
||||
return
|
||||
self.canvas.set_path(options['shape.path'] or [])
|
||||
|
||||
def path(self):
|
||||
return self.canvas.path
|
||||
|
||||
def path_rect(self):
|
||||
return get_global_rect(
|
||||
[QtCore.QPointF(*p['point']) for p in self.canvas.path])
|
||||
|
||||
|
||||
class PathEditorCanvas(QtWidgets.QWidget):
|
||||
pathEdited = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(PathEditorCanvas, self).__init__(parent)
|
||||
self.viewportmapper = ViewportMapper()
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
self.selection_square = SelectionSquare()
|
||||
self.manipulator = Manipulator(self.viewportmapper)
|
||||
self.transform = Transform()
|
||||
self.selection = PointSelection()
|
||||
self.interaction_manager = InteractionManager()
|
||||
self.setMouseTracking(True)
|
||||
self.path = []
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(300, 200)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self))
|
||||
self.transform.direction = self.manipulator.get_direction(event.pos())
|
||||
self.current_action = self.get_action()
|
||||
if self.current_action and self.current_action[0] == 'move point':
|
||||
self.selection.set([self.current_action[1]])
|
||||
self.update_manipulator_rect()
|
||||
if self.manipulator.rect is not None:
|
||||
self.transform.set_rect(self.manipulator.rect)
|
||||
self.transform.reference_rect = QtCore.QRectF(self.manipulator.rect)
|
||||
self.transform.set_reference_point(cursor)
|
||||
|
||||
has_shape_hovered = bool(self.current_action)
|
||||
self.interaction_manager.update(
|
||||
event, pressed=True,
|
||||
has_shape_hovered=has_shape_hovered,
|
||||
dragging=has_shape_hovered)
|
||||
self.selection_square.clicked(cursor)
|
||||
self.update()
|
||||
|
||||
def get_action(self):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self))
|
||||
if self.manipulator.rect and self.manipulator.rect.contains(cursor):
|
||||
return 'move points', None
|
||||
direction = self.manipulator.get_direction(get_cursor(self))
|
||||
if direction:
|
||||
return 'resize points', direction
|
||||
tolerance = self.viewportmapper.to_units(5)
|
||||
for i, data in enumerate(self.path):
|
||||
point = QtCore.QPointF(*data['point'])
|
||||
if distance(point, cursor) < tolerance:
|
||||
return 'move point', i
|
||||
if data['tangent_in']:
|
||||
point = QtCore.QPointF(*data['tangent_in'])
|
||||
if distance(point, cursor) < tolerance:
|
||||
return 'move in', i
|
||||
if data['tangent_out']:
|
||||
point = QtCore.QPointF(*data['tangent_out'])
|
||||
if distance(point, cursor) < tolerance:
|
||||
return 'move out', i
|
||||
index = is_point_on_path_edge(self.path, cursor, tolerance)
|
||||
if index is not None:
|
||||
return 'create point', index
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
cursor = self.viewportmapper.to_units_coords(get_cursor(self)).toPoint()
|
||||
if self.interaction_manager.mode == InteractionManager.NAVIGATION:
|
||||
offset = self.interaction_manager.mouse_offset(event.pos())
|
||||
if offset is not None:
|
||||
origin = self.viewportmapper.origin - offset
|
||||
self.viewportmapper.origin = origin
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
self.selection_square.handle(cursor)
|
||||
|
||||
elif self.interaction_manager.mode == InteractionManager.DRAGGING:
|
||||
if not self.current_action:
|
||||
return self.update()
|
||||
|
||||
offset = self.interaction_manager.mouse_offset(event.pos())
|
||||
if not offset:
|
||||
return self.update()
|
||||
|
||||
offset = QtCore.QPointF(
|
||||
self.viewportmapper.to_units(offset.x()),
|
||||
self.viewportmapper.to_units(offset.y()))
|
||||
|
||||
if self.current_action[0] == 'move points':
|
||||
offset_path(self.path, offset, self.selection)
|
||||
self.update_manipulator_rect()
|
||||
|
||||
elif self.current_action[0] == 'resize points':
|
||||
resize_rect_with_direction(
|
||||
self.transform.rect, cursor,
|
||||
self.transform.direction)
|
||||
path = (
|
||||
[self.path[i] for i in self.selection] if
|
||||
self.selection else self.path)
|
||||
resize_path_with_reference(
|
||||
path,
|
||||
self.transform.reference_rect,
|
||||
self.transform.rect)
|
||||
rect = self.transform.rect
|
||||
self.transform.reference_rect.setTopLeft(rect.topLeft())
|
||||
self.transform.reference_rect.setSize(rect.size())
|
||||
self.manipulator.set_rect(self.transform.rect)
|
||||
|
||||
elif self.current_action[0] == 'move point':
|
||||
offset_path(self.path, offset, [self.current_action[1]])
|
||||
|
||||
elif self.current_action and self.current_action[0] == 'move in':
|
||||
move_tangent(
|
||||
point=self.path[self.current_action[1]],
|
||||
tangent_in_moved=True,
|
||||
offset=offset,
|
||||
lock=not self.interaction_manager.ctrl_pressed)
|
||||
|
||||
elif self.current_action[0] == 'move out':
|
||||
move_tangent(
|
||||
point=self.path[self.current_action[1]],
|
||||
tangent_in_moved=False,
|
||||
offset=offset,
|
||||
lock=not self.interaction_manager.ctrl_pressed)
|
||||
|
||||
elif self.current_action[0] == 'create point':
|
||||
self.interaction_manager.mouse_offset(event.pos())
|
||||
point = {
|
||||
'point': [cursor.x(), cursor.y()],
|
||||
'tangent_in': None,
|
||||
'tangent_out': None}
|
||||
index = self.current_action[1] + 1
|
||||
self.path.insert(index, point)
|
||||
self.autotangent(index)
|
||||
self.current_action = 'move point', index
|
||||
self.selection.set([index])
|
||||
self.update_manipulator_rect()
|
||||
|
||||
self.update()
|
||||
|
||||
def move_point(self, i, offset):
|
||||
self.path[i]['point'][0] += offset.x()
|
||||
self.path[i]['point'][1] += offset.y()
|
||||
point = self.path[i]['tangent_in']
|
||||
if point:
|
||||
point[0] += offset.x()
|
||||
point[1] += offset.y()
|
||||
point = self.path[i]['tangent_out']
|
||||
if point:
|
||||
point[0] += offset.x()
|
||||
point[1] += offset.y()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
if self.current_action:
|
||||
self.pathEdited.emit()
|
||||
|
||||
if self.interaction_manager.mode == InteractionManager.SELECTION:
|
||||
self.select()
|
||||
|
||||
self.selection_square.release()
|
||||
self.interaction_manager.update(
|
||||
event, pressed=False, has_shape_hovered=False, dragging=False)
|
||||
self.update()
|
||||
|
||||
def select(self):
|
||||
shift = self.interaction_manager.shift_pressed
|
||||
ctrl = self.interaction_manager.ctrl_pressed
|
||||
self.selection.mode = get_selection_mode(shift=shift, ctrl=ctrl)
|
||||
rect = self.selection_square.rect
|
||||
points = []
|
||||
indexes = []
|
||||
for i, p in enumerate(self.path):
|
||||
point = QtCore.QPointF(*p['point'])
|
||||
if rect.contains(point):
|
||||
indexes.append(i)
|
||||
points.append(point)
|
||||
self.selection.set(indexes)
|
||||
self.update_manipulator_rect()
|
||||
|
||||
def update_manipulator_rect(self, points=None):
|
||||
if points is None:
|
||||
points = [
|
||||
QtCore.QPointF(*self.path[i]['point'])
|
||||
for i in self.selection]
|
||||
if len(points) < 2:
|
||||
self.manipulator.set_rect(None)
|
||||
return
|
||||
|
||||
rect = get_global_rect(points)
|
||||
rect.setHeight(max(rect.height(), .5))
|
||||
rect.setWidth(max(rect.width(), .5))
|
||||
self.manipulator.set_rect(rect)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
# To center the zoom on the mouse, we save a reference mouse position
|
||||
# and compare the offset after zoom computation.
|
||||
factor = .25 if event.angleDelta().y() > 0 else -.25
|
||||
self.zoom(factor, event.pos())
|
||||
self.update()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self.viewportmapper.viewsize = self.size()
|
||||
size = (event.size() - event.oldSize()) / 2
|
||||
offset = QtCore.QPointF(size.width(), size.height())
|
||||
self.viewportmapper.origin -= offset
|
||||
self.update()
|
||||
|
||||
def zoom(self, factor, reference):
|
||||
abspoint = self.viewportmapper.to_units_coords(reference)
|
||||
if factor > 0:
|
||||
self.viewportmapper.zoomin(abs(factor))
|
||||
else:
|
||||
self.viewportmapper.zoomout(abs(factor))
|
||||
relcursor = self.viewportmapper.to_viewport_coords(abspoint)
|
||||
vector = relcursor - reference
|
||||
self.viewportmapper.origin = self.viewportmapper.origin + vector
|
||||
|
||||
def paintEvent(self, event):
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
try:
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setPen(QtGui.QPen())
|
||||
color = QtGui.QColor('black')
|
||||
color.setAlpha(50)
|
||||
painter.setBrush(color)
|
||||
rect = QtCore.QRect(
|
||||
0, 0, self.rect().width() - 1, self.rect().height() - 1)
|
||||
painter.drawRect(rect)
|
||||
draw_world_coordinates(
|
||||
painter, self.rect(), QtGui.QColor('#282828'),
|
||||
self.viewportmapper)
|
||||
painter.setBrush(QtGui.QBrush())
|
||||
draw_shape_path(
|
||||
painter, self.path, self.selection, self.viewportmapper)
|
||||
draw_tangents(painter, self.path, self.viewportmapper)
|
||||
if self.selection_square.rect:
|
||||
draw_selection_square(
|
||||
painter, self.selection_square.rect, self.viewportmapper)
|
||||
|
||||
conditions = (
|
||||
self.manipulator.rect is not None and
|
||||
all(self.manipulator.viewport_handlers()))
|
||||
|
||||
if conditions:
|
||||
draw_manipulator(
|
||||
painter, self.manipulator,
|
||||
get_cursor(self), self.viewportmapper)
|
||||
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
def center_path(self):
|
||||
qpath = path_to_qpath(self.path, ViewportMapper())
|
||||
center = qpath.boundingRect().center()
|
||||
offset_path(self.path, -center)
|
||||
self.pathEdited.emit()
|
||||
self.update_manipulator_rect()
|
||||
self.update()
|
||||
|
||||
def delete(self):
|
||||
if len(self.path) - len(self.selection) < 3:
|
||||
return QtWidgets.QMessageBox.critical(
|
||||
self, 'Error', 'Shape must at least contains 3 control points')
|
||||
|
||||
for i in sorted(self.selection, reverse=True):
|
||||
del self.path[i]
|
||||
self.selection.clear()
|
||||
self.update_manipulator_rect()
|
||||
self.pathEdited.emit()
|
||||
self.update()
|
||||
|
||||
def break_tangents(self):
|
||||
for i in self.selection:
|
||||
self.path[i]['tangent_in'] = None
|
||||
self.path[i]['tangent_out'] = None
|
||||
self.pathEdited.emit()
|
||||
self.update()
|
||||
|
||||
def smooth_tangents(self):
|
||||
if not self.selection:
|
||||
return
|
||||
|
||||
for i in self.selection:
|
||||
self.autotangent(i)
|
||||
self.update()
|
||||
self.pathEdited.emit()
|
||||
|
||||
def autotangent(self, i):
|
||||
point = self.path[i]['point']
|
||||
next_index = i + 1 if i < (len(self.path) - 1) else 0
|
||||
next_point = self.path[next_index]['point']
|
||||
previous_point = self.path[i - 1]['point']
|
||||
tan_in, tan_out = auto_tangent(point, previous_point, next_point)
|
||||
self.path[i]['tangent_in'] = tan_in
|
||||
self.path[i]['tangent_out'] = tan_out
|
||||
|
||||
def set_path(self, path):
|
||||
self.path = path
|
||||
self.selection.clear()
|
||||
self.manipulator.set_rect(None)
|
||||
self.focus()
|
||||
|
||||
def focus(self):
|
||||
if not self.path:
|
||||
self.update()
|
||||
return
|
||||
points = [QtCore.QPointF(*p['point']) for p in self.path]
|
||||
rect = get_global_rect(points)
|
||||
self.viewportmapper.focus(grow_rect(rect, 15))
|
||||
self.update()
|
||||
|
||||
def symmetry(self, horizontal=True):
|
||||
path = (
|
||||
[self.path[i] for i in self.selection] if
|
||||
self.selection else self.path)
|
||||
|
||||
if self.manipulator.rect:
|
||||
center = self.manipulator.rect.center()
|
||||
else:
|
||||
points = [QtCore.QPointF(*p['point']) for p in self.path]
|
||||
rect = get_global_rect(points)
|
||||
center = rect.center()
|
||||
|
||||
path_symmetry(path, center, horizontal=horizontal)
|
||||
self.pathEdited.emit()
|
||||
self.update()
|
||||
|
||||
|
||||
class PointSelection():
|
||||
def __init__(self):
|
||||
self.elements = []
|
||||
self.mode = 'replace'
|
||||
|
||||
def set(self, elements):
|
||||
if self.mode == 'add':
|
||||
if elements is None:
|
||||
return
|
||||
return self.add(elements)
|
||||
elif self.mode == 'replace':
|
||||
if elements is None:
|
||||
return self.clear()
|
||||
return self.replace(elements)
|
||||
elif self.mode == 'invert':
|
||||
if elements is None:
|
||||
return
|
||||
return self.invert(elements)
|
||||
elif self.mode == 'remove':
|
||||
if elements is None:
|
||||
return
|
||||
for element in elements:
|
||||
if element in self.elements:
|
||||
self.remove(element)
|
||||
|
||||
def replace(self, elements):
|
||||
self.elements = elements
|
||||
|
||||
def add(self, elements):
|
||||
self.elements.extend([e for e in elements if e not in self])
|
||||
|
||||
def remove(self, element):
|
||||
self.elements.remove(element)
|
||||
|
||||
def invert(self, elements):
|
||||
for element in elements:
|
||||
if element not in self.elements:
|
||||
self.add([element])
|
||||
else:
|
||||
self.remove(element)
|
||||
|
||||
def clear(self):
|
||||
self.elements = []
|
||||
|
||||
def __len__(self):
|
||||
return len(self.elements)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.elements)
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.elements[i]
|
||||
|
||||
def __iter__(self):
|
||||
return self.elements.__iter__()
|
||||
|
||||
|
||||
def path_to_qpath(path, viewportmapper):
|
||||
painter_path = QtGui.QPainterPath()
|
||||
start = QtCore.QPointF(*path[0]['point'])
|
||||
painter_path.moveTo(viewportmapper.to_viewport_coords(start))
|
||||
for i in range(len(path)):
|
||||
point = path[i]
|
||||
point2 = path[i + 1 if i + 1 < len(path) else 0]
|
||||
c1 = QtCore.QPointF(*(point['tangent_out'] or point['point']))
|
||||
c2 = QtCore.QPointF(*(point2['tangent_in'] or point2['point']))
|
||||
end = QtCore.QPointF(*point2['point'])
|
||||
painter_path.cubicTo(
|
||||
viewportmapper.to_viewport_coords(c1),
|
||||
viewportmapper.to_viewport_coords(c2),
|
||||
viewportmapper.to_viewport_coords(end))
|
||||
return painter_path
|
||||
|
||||
|
||||
def draw_shape_path(painter, path, selection, viewportmapper):
|
||||
painter.setPen(QtCore.Qt.gray)
|
||||
painter.drawPath(path_to_qpath(path, viewportmapper))
|
||||
rect = QtCore.QRectF(0, 0, 5, 5)
|
||||
for i, point in enumerate(path):
|
||||
center = QtCore.QPointF(*point['point'])
|
||||
rect.moveCenter(viewportmapper.to_viewport_coords(center))
|
||||
painter.setBrush(QtCore.Qt.white if i in selection else QtCore.Qt.NoBrush)
|
||||
painter.drawRect(rect)
|
||||
|
||||
|
||||
def is_point_on_path_edge(path, cursor, tolerance=3):
|
||||
stroker = QtGui.QPainterPathStroker()
|
||||
stroker.setWidth(tolerance * 2)
|
||||
|
||||
for i in range(len(path)):
|
||||
point = path[i]
|
||||
painter_path = QtGui.QPainterPath()
|
||||
painter_path.moveTo(QtCore.QPointF(*point['point']))
|
||||
|
||||
point2 = path[i + 1 if i + 1 < len(path) else 0]
|
||||
c1 = QtCore.QPointF(*(point['tangent_out'] or point['point']))
|
||||
c2 = QtCore.QPointF(*(point2['tangent_in'] or point2['point']))
|
||||
end = QtCore.QPointF(*point2['point'])
|
||||
painter_path.cubicTo(c1, c2, end)
|
||||
|
||||
stroke = stroker.createStroke(painter_path)
|
||||
if stroke.contains(cursor):
|
||||
return i
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def move_tangent(point, tangent_in_moved, offset, lock):
|
||||
center_point = point['point']
|
||||
tangent_in = point['tangent_in' if tangent_in_moved else 'tangent_out']
|
||||
tangent_out = point['tangent_out' if tangent_in_moved else 'tangent_in']
|
||||
offset = offset.x(), offset.y()
|
||||
tangent_in, tangent_out = offset_tangent(
|
||||
tangent_in, tangent_out, center_point, offset, lock)
|
||||
point['tangent_in'if tangent_in_moved else 'tangent_out'] = tangent_in
|
||||
point['tangent_out'if tangent_in_moved else 'tangent_in'] = tangent_out
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
se.close()
|
||||
except:
|
||||
pass
|
||||
from ..qtutils import maya_main_window
|
||||
se = PathEditor(maya_main_window())
|
||||
se.setWindowFlags(QtCore.Qt.Window)
|
||||
se.show()
|
||||
374
2023/scripts/animation_tools/dwpicker/designer/stackeditor.py
Normal file
374
2023/scripts/animation_tools/dwpicker/designer/stackeditor.py
Normal file
@@ -0,0 +1,374 @@
|
||||
from decimal import Decimal, getcontext
|
||||
from ..pyside import QtWidgets, QtGui, QtCore
|
||||
|
||||
|
||||
HANDLER_WIDTH = 16
|
||||
HANDLER_HEIGHT = 16
|
||||
|
||||
|
||||
class StackEditor(QtWidgets.QWidget):
|
||||
panelsChanged = QtCore.Signal(object)
|
||||
panelSelected = QtCore.Signal(int)
|
||||
panelDoubleClicked = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(StackEditor, self).__init__(parent)
|
||||
self.data = [[1., [1.]]]
|
||||
self.orientation = 'vertical'
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
self.setMouseTracking(True)
|
||||
self.clicked_action = None
|
||||
self.selected_index = None
|
||||
self.panels_are_changed = False
|
||||
self.panel_is_selected = None
|
||||
|
||||
def set_orientation(self, orientation):
|
||||
self.orientation = orientation
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
self.update()
|
||||
|
||||
def set_data(self, data):
|
||||
self.data = data
|
||||
self.update()
|
||||
|
||||
def sizeHint(self):
|
||||
return QtCore.QSize(300, 210)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
self.clicked_action = self.get_action(event.pos())
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
if event.button() != QtCore.Qt.LeftButton:
|
||||
return
|
||||
clicked_action = self.get_action(event.pos())
|
||||
if clicked_action[0] == 'select':
|
||||
panel = self.panel_number(clicked_action[1])
|
||||
self.panelSelected.emit(panel)
|
||||
self.panelDoubleClicked.emit(panel)
|
||||
self.selected_index = clicked_action[1]
|
||||
self.update()
|
||||
|
||||
def panel_number(self, index):
|
||||
k = 1
|
||||
for i, (_, rows) in enumerate(self.data):
|
||||
for j in range(len(rows)):
|
||||
if [i, j] == index:
|
||||
return k
|
||||
k += 1
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() != QtCore.Qt.LeftButton or not self.clicked_action:
|
||||
return
|
||||
index = self.clicked_action[1]
|
||||
if self.clicked_action[0] == 'delete':
|
||||
delete_panel(self.data, index)
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
self.panelsChanged.emit(self.data)
|
||||
elif self.clicked_action[0] == 'select':
|
||||
index = self.clicked_action[1]
|
||||
if index == self.selected_index:
|
||||
self.selected_index = None
|
||||
self.panelSelected.emit(-1)
|
||||
else:
|
||||
self.selected_index = index
|
||||
self.panelSelected.emit(self.panel_number(self.selected_index))
|
||||
else:
|
||||
self.check_buffer_states()
|
||||
self.update()
|
||||
self.clicked_action = None
|
||||
|
||||
def check_buffer_states(self):
|
||||
if self.panel_is_selected is not None:
|
||||
self.panelSelected.emit(self.panel_is_selected)
|
||||
if self.panels_are_changed:
|
||||
self.panelsChanged.emit(self.data)
|
||||
self.panel_is_selected = None
|
||||
self.panels_are_changed = False
|
||||
|
||||
def get_action(self, cursor):
|
||||
for i, column in enumerate(self.stack_rects):
|
||||
for j, rect in enumerate(column):
|
||||
if not rect.contains(cursor):
|
||||
continue
|
||||
|
||||
if get_close_handler_rect(rect).contains(cursor):
|
||||
if i + j:
|
||||
return 'delete', [i, j]
|
||||
|
||||
hrect = get_horizontal_handler_rect(rect)
|
||||
vrect = get_vertical_handler_rect(rect)
|
||||
if self.orientation == 'horizontal':
|
||||
hrect, vrect = vrect, hrect
|
||||
|
||||
if hrect.contains(cursor):
|
||||
if j == len(column) - 1:
|
||||
return 'create vertical', [i, j]
|
||||
return 'move vertical', [i, j]
|
||||
|
||||
if vrect.contains(cursor):
|
||||
if i == len(self.data) - 1:
|
||||
return 'create horizontal', [i, j]
|
||||
return 'move horizontal', [i, j]
|
||||
|
||||
return 'select', [i, j]
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if not self.clicked_action:
|
||||
return
|
||||
vertical = self.orientation == 'vertical'
|
||||
|
||||
if self.clicked_action[0] == 'create vertical':
|
||||
index = self.clicked_action[1]
|
||||
col = self.data[index[0]][1]
|
||||
col[-1] -= .1
|
||||
col.append(.1)
|
||||
self.clicked_action = 'move vertical', index
|
||||
self.selected_index = [index[0], index[1] + 1]
|
||||
self.panel_is_selected = self.panel_number(self.selected_index)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'create horizontal':
|
||||
index = self.clicked_action[1]
|
||||
self.data[-1][0] -= .1
|
||||
self.data.append([.1, [1.]])
|
||||
self.clicked_action = 'move horizontal', index
|
||||
self.selected_index = [index[0] + 1, 0]
|
||||
self.panel_is_selected = self.panel_number(self.selected_index)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'move vertical' and vertical:
|
||||
index = self.clicked_action[1]
|
||||
y = event.pos().y() / self.height()
|
||||
move_vertical(self.data, index, y)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'move vertical':
|
||||
index = self.clicked_action[1]
|
||||
x = event.pos().x() / self.width()
|
||||
move_vertical(self.data, index, x)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'move horizontal' and vertical:
|
||||
index = self.clicked_action[1]
|
||||
x = event.pos().x() / self.width()
|
||||
move_horizontal(self.data, index, x)
|
||||
self.panels_are_changed = True
|
||||
|
||||
elif self.clicked_action[0] == 'move horizontal':
|
||||
index = self.clicked_action[1]
|
||||
y = event.pos().y() / self.height()
|
||||
move_horizontal(self.data, index, y)
|
||||
self.panels_are_changed = True
|
||||
|
||||
self.stack_rects = get_stack_rects(
|
||||
self.data, self.rect(), self.orientation)
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, _):
|
||||
painter = QtGui.QPainter(self)
|
||||
k = 1
|
||||
original_pen = painter.pen()
|
||||
original_brush = painter.brush()
|
||||
for i, column in enumerate(self.stack_rects):
|
||||
for j, rect in enumerate(column):
|
||||
if [i, j] == self.selected_index:
|
||||
pen = QtGui.QPen(QtGui.QColor('yellow'))
|
||||
pen.setWidth(5)
|
||||
painter.setPen(pen)
|
||||
brush = QtGui.QBrush(original_brush)
|
||||
color = brush.color()
|
||||
color.setAlpha(50)
|
||||
brush.setColor(color)
|
||||
brush.setStyle(QtCore.Qt.FDiagPattern)
|
||||
painter.setBrush(brush)
|
||||
else:
|
||||
pen = original_pen
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(original_brush)
|
||||
|
||||
painter.drawRect(rect)
|
||||
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.setBrush(pen.color())
|
||||
handler_rect = get_horizontal_handler_rect(rect)
|
||||
painter.drawPath(up_arrow(handler_rect))
|
||||
handler_rect = get_vertical_handler_rect(rect)
|
||||
painter.drawPath(left_arrow(handler_rect))
|
||||
|
||||
painter.setPen(original_pen)
|
||||
painter.setBrush(original_brush)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(15)
|
||||
font.setBold(True)
|
||||
painter.setFont(font)
|
||||
painter.drawText(rect, QtCore.Qt.AlignCenter, str(k))
|
||||
k += 1
|
||||
if i + j == 0:
|
||||
continue
|
||||
font = QtGui.QFont()
|
||||
font.setBold(True)
|
||||
painter.setFont(font)
|
||||
painter.drawText(
|
||||
get_close_handler_rect(rect), QtCore.Qt.AlignCenter, 'X')
|
||||
painter.end()
|
||||
|
||||
|
||||
def get_stack_rects(data, rect, orientation):
|
||||
if orientation == 'vertical':
|
||||
return get_vertical_stack_rects(data, rect)
|
||||
return get_horizontal_stack_rects(data, rect)
|
||||
|
||||
|
||||
def get_horizontal_stack_rects(data, rect):
|
||||
result = []
|
||||
y = 0
|
||||
for height, rows in data:
|
||||
column = []
|
||||
x = 0
|
||||
for width in rows:
|
||||
panel_rect = QtCore.QRectF(
|
||||
x * rect.width(),
|
||||
y * rect.height(),
|
||||
(width * rect.width()) - 1,
|
||||
(height * rect.height()) - 1)
|
||||
column.append(panel_rect)
|
||||
x += width
|
||||
y += height
|
||||
result.append(column)
|
||||
return result
|
||||
|
||||
|
||||
def get_vertical_stack_rects(data, rect):
|
||||
result = []
|
||||
x = 0
|
||||
for width, rows in data:
|
||||
column = []
|
||||
y = 0
|
||||
for height in rows:
|
||||
panel_rect = QtCore.QRectF(
|
||||
x * rect.width(),
|
||||
y * rect.height(),
|
||||
(width * rect.width()) - 1,
|
||||
(height * rect.height()) - 1)
|
||||
column.append(panel_rect)
|
||||
y += height
|
||||
x += width
|
||||
result.append(column)
|
||||
return result
|
||||
|
||||
|
||||
def get_vertical_handler_rect(rect):
|
||||
return QtCore.QRectF(
|
||||
rect.right() - HANDLER_WIDTH,
|
||||
rect.center().y() - (HANDLER_HEIGHT / 2),
|
||||
HANDLER_WIDTH, HANDLER_HEIGHT)
|
||||
|
||||
|
||||
def get_horizontal_handler_rect(rect):
|
||||
return QtCore.QRectF(
|
||||
rect.center().x() - (HANDLER_WIDTH / 2),
|
||||
rect.bottom() - HANDLER_HEIGHT,
|
||||
HANDLER_WIDTH, HANDLER_HEIGHT)
|
||||
|
||||
|
||||
def get_close_handler_rect(rect):
|
||||
return QtCore.QRectF(
|
||||
rect.right() - HANDLER_WIDTH,
|
||||
rect.top(), HANDLER_HEIGHT, HANDLER_WIDTH)
|
||||
|
||||
|
||||
def delete_panel(data, index):
|
||||
column = data[index[0]][1]
|
||||
if len(column) > 1:
|
||||
data[index[0]][1] = delete_value(column, index[1])
|
||||
return
|
||||
values = delete_value([c[0] for c in data], index[0])
|
||||
del data[index[0]]
|
||||
for i, value in enumerate(values):
|
||||
data[i][0] = value
|
||||
|
||||
|
||||
def delete_value(values, index):
|
||||
getcontext().prec = 50
|
||||
decimal_values = [Decimal(v) for v in values]
|
||||
values = [
|
||||
Decimal(v) for i, v in enumerate(decimal_values) if i != index]
|
||||
return [
|
||||
float((v / sum(values)).quantize(Decimal('1.00000')))
|
||||
for v in values]
|
||||
|
||||
|
||||
def move_vertical(data, index, y):
|
||||
column = data[index[0]][1]
|
||||
ratios = to_ratios(column)
|
||||
if index[1] == 0:
|
||||
y = max((.1, y))
|
||||
else:
|
||||
y = max((ratios[index[1] - 1] + .1, y))
|
||||
y = min((y, ratios[index[1] + 1] - .1))
|
||||
ratios[index[1]] = y
|
||||
data[index[0]][1] = to_weights(ratios)
|
||||
|
||||
|
||||
def move_horizontal(data, index, x):
|
||||
ratios = to_ratios(c[0] for c in data)
|
||||
if index[0] == 0:
|
||||
x = max((.1, x))
|
||||
else:
|
||||
x = max((ratios[index[0] - 1] + .1, x))
|
||||
x = min((x, ratios[index[0] + 1] - .1))
|
||||
ratios[index[0]] = x
|
||||
for i, col in enumerate(to_weights(ratios)):
|
||||
data[i][0] = col
|
||||
|
||||
|
||||
def up_arrow(rect):
|
||||
path = QtGui.QPainterPath(rect.bottomLeft())
|
||||
path.lineTo(rect.bottomRight())
|
||||
point = QtCore.QPointF(rect.center().x(), rect.top())
|
||||
path.lineTo(point)
|
||||
path.lineTo(rect.bottomLeft())
|
||||
return path
|
||||
|
||||
|
||||
def left_arrow(rect):
|
||||
path = QtGui.QPainterPath(rect.topRight())
|
||||
path.lineTo(rect.bottomRight())
|
||||
point = QtCore.QPointF(rect.left(), rect.center().y())
|
||||
path.lineTo(point)
|
||||
path.lineTo(rect.topRight())
|
||||
return path
|
||||
|
||||
|
||||
def to_ratios(weights):
|
||||
"""
|
||||
Convert weight list to ratios.
|
||||
input: [0.2, 0.3, 0.4, 0.1]
|
||||
output: [0.2, 0.5, 0.9, 1.0]
|
||||
"""
|
||||
total = 0.0
|
||||
result = []
|
||||
for weight in weights:
|
||||
total += weight
|
||||
result.append(total)
|
||||
return result
|
||||
|
||||
|
||||
def to_weights(ratios):
|
||||
"""
|
||||
Convert ratio list to weights.
|
||||
input: [0.2, 0.5, 0.9, 1.0]
|
||||
output: [0.2, 0.3, 0.4, 0.1]
|
||||
"""
|
||||
result = []
|
||||
result.extend(ratio - sum(result) for ratio in ratios)
|
||||
return result
|
||||
Reference in New Issue
Block a user