811 lines
30 KiB
Python
811 lines
30 KiB
Python
from copy import deepcopy
|
|
from functools import partial
|
|
|
|
from maya import cmds
|
|
import maya.OpenMaya as om
|
|
from .pyside import QtWidgets, QtGui, QtCore
|
|
|
|
from .align import align_shapes_on_line
|
|
from .compatibility import ensure_general_options_sanity
|
|
from .document import PickerDocument
|
|
from .dialog import warning, CommandEditorDialog
|
|
from .interactive import SelectionSquare
|
|
from .interactionmanager import InteractionManager
|
|
from .geometry import get_combined_rects, get_connection_path
|
|
from .languages import execute_code
|
|
from .optionvar import (
|
|
save_optionvar, DEFAULT_BG_COLOR, DEFAULT_TEXT_COLOR, DEFAULT_WIDTH,
|
|
DEFAULT_HEIGHT, DEFAULT_LABEL, DISPLAY_HIERARCHY_IN_PICKER,
|
|
LAST_COMMAND_LANGUAGE, SYNCHRONYZE_SELECTION, ZOOM_SENSITIVITY)
|
|
from .painting import (
|
|
draw_shape, draw_selection_square, draw_picker_focus, draw_connections)
|
|
from .qtutils import get_cursor, clear_layout
|
|
from .shape import (
|
|
build_multiple_shapes, cursor_in_shape, rect_intersects_shape)
|
|
from .stack import create_stack_splitters, count_panels
|
|
from .selection import (
|
|
select_targets, select_shapes_from_selection, get_selection_mode,
|
|
NameclashError)
|
|
from .templates import BUTTON, COMMAND
|
|
from .viewport import ViewportMapper
|
|
|
|
|
|
SPLITTER_STYLE = """\
|
|
QSplitter::handle {
|
|
background-color: rgba(0, 0, 0, 50);
|
|
border: 1px solid #444;
|
|
width: 2px;
|
|
height: 2px;
|
|
}
|
|
"""
|
|
|
|
|
|
def set_shapes_hovered(
|
|
shapes,
|
|
world_cursor,
|
|
viewport_cursor,
|
|
selection_rect,
|
|
viewport_selection_rect,
|
|
viewportmapper=None):
|
|
"""
|
|
It set hovered the shape if his rect contains the cursor.
|
|
"""
|
|
if not shapes:
|
|
return
|
|
world_cursor = world_cursor.toPoint()
|
|
shapes = [s for s in shapes if not s.is_background()]
|
|
selection_shapes_intersect_selection = [
|
|
s for s in shapes
|
|
if cursor_in_shape(s, world_cursor, viewport_cursor, False, viewportmapper)
|
|
or rect_intersects_shape(
|
|
shape=s,
|
|
unit_rect=selection_rect,
|
|
viewport_rect=viewport_selection_rect,
|
|
force_world_space=False,
|
|
viewportmapper=viewportmapper)]
|
|
targets = list_targets(selection_shapes_intersect_selection)
|
|
for s in shapes:
|
|
if s.targets():
|
|
# Set all buttons hovered from his targets contents.
|
|
# I the physically hovered buttons contains targets, this will
|
|
# highlight all buttons containing similare targets.
|
|
state = next((False for t in s.targets() if t not in targets), True)
|
|
elif not s.is_background():
|
|
# Simple highlighting method for the interactive buttons.
|
|
state = s in selection_shapes_intersect_selection
|
|
else:
|
|
state = False
|
|
s.hovered = state
|
|
|
|
|
|
def detect_hovered_shape(shapes, world_cursor, screen_cursor, viewportmapper):
|
|
if not shapes:
|
|
return
|
|
for shape in reversed(shapes):
|
|
hovered = cursor_in_shape(
|
|
shape=shape,
|
|
world_cursor=world_cursor,
|
|
viewpoert_cursor=screen_cursor,
|
|
force_world_space=False,
|
|
viewportmapper=viewportmapper)
|
|
if hovered and not shape.is_background():
|
|
return shape
|
|
|
|
|
|
def list_targets(shapes):
|
|
return {t for s in shapes for t in s.targets()}
|
|
|
|
|
|
class PickerStackedView(QtWidgets.QWidget):
|
|
|
|
def __init__(self, document=None, editable=True, parent=None):
|
|
super(PickerStackedView, self).__init__(parent)
|
|
self.document = document or PickerDocument.create()
|
|
mtd = self.general_option_changed
|
|
self.document.general_option_changed.connect(mtd)
|
|
self.document.data_changed.connect(self.full_refresh)
|
|
self.editable = editable
|
|
self.pickers = []
|
|
self.widget = None
|
|
self.last_selected_tab = None
|
|
|
|
self.layers_menu = VisibilityLayersMenu(document)
|
|
self.layers_menu.visibilities_changed.connect(self.update)
|
|
|
|
self.as_sub_tab = document.data['general']['panels.as_sub_tab']
|
|
self.layout = QtWidgets.QHBoxLayout(self)
|
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
|
self.setStyleSheet(SPLITTER_STYLE)
|
|
self.create_pickers()
|
|
self.create_panels()
|
|
|
|
def register_callbacks(self):
|
|
for picker in self.pickers:
|
|
picker.register_callbacks()
|
|
|
|
def unregister_callbacks(self):
|
|
for picker in self.pickers:
|
|
picker.unregister_callbacks()
|
|
|
|
def reset(self, force_all=False):
|
|
if not force_all and not isinstance(self.widget, QtWidgets.QTabWidget):
|
|
for picker in self.pickers:
|
|
if picker.rect().contains(get_cursor(picker)):
|
|
picker.reset()
|
|
return picker.panel
|
|
|
|
elif not force_all:
|
|
picker = self.pickers[self.widget.currentIndex()]
|
|
picker.reset()
|
|
return
|
|
|
|
# If no picker hovered, focus all.
|
|
if self.document.data['general']['panels.as_sub_tab']:
|
|
viewsize = self.pickers[0].viewportmapper.viewsize
|
|
else:
|
|
viewsize = None
|
|
for picker in self.pickers:
|
|
picker.reset(viewsize)
|
|
|
|
def create_pickers(self):
|
|
self.unregister_callbacks()
|
|
self.pickers = [
|
|
PickerPanelView(
|
|
self.document, self.editable, i, self.layers_menu, self)
|
|
for i in range(self.document.panel_count())]
|
|
for picker in self.pickers:
|
|
picker.size_event_triggered.connect(self.picker_resized)
|
|
self.register_callbacks()
|
|
|
|
def picker_resized(self, event):
|
|
data = self.document.data
|
|
if not data['general']['panels.as_sub_tab']:
|
|
return
|
|
for i, picker in enumerate(self.pickers):
|
|
if i == self.widget.currentIndex():
|
|
continue
|
|
picker.adjust_center(event.size(), event.oldSize())
|
|
|
|
def copy_pickers(self):
|
|
self.pickers = [p.copy() for p in self.pickers]
|
|
for picker in self.pickers:
|
|
picker.size_event_triggered.connect(self.picker_resized)
|
|
|
|
def create_panels(self, panel=None):
|
|
data = self.document.data
|
|
if not self.as_sub_tab:
|
|
panels = data['general']['panels']
|
|
orientation = data['general']['panels.orientation']
|
|
self.widget = create_stack_splitters(
|
|
panels, self.pickers, orientation)
|
|
else:
|
|
self.widget = QtWidgets.QTabWidget()
|
|
names = data['general']['panels.names']
|
|
for picker, name in zip(self.pickers, names):
|
|
self.widget.addTab(picker, name)
|
|
|
|
# Check "if panel is not None" (0 is a valid value,
|
|
# so "if panel" would be incorrect)
|
|
if panel is not None:
|
|
self.widget.setCurrentIndex(panel)
|
|
self.last_selected_tab = panel
|
|
elif self.last_selected_tab:
|
|
self.widget.setCurrentIndex(self.last_selected_tab)
|
|
self.widget.currentChanged.connect(self.on_tab_changed)
|
|
|
|
clear_layout(self.layout)
|
|
self.layout.addWidget(self.widget)
|
|
|
|
def on_tab_changed(self, index):
|
|
self.last_selected_tab = index
|
|
|
|
def full_refresh(self):
|
|
panels = self.document.data['general']['panels']
|
|
if count_panels(panels) != len(self.pickers):
|
|
self.create_pickers()
|
|
self.create_panels()
|
|
|
|
def general_option_changed(self, _, option):
|
|
value = self.document.data['general'][option]
|
|
panels = self.document.data['general']['panels']
|
|
reset = False
|
|
if option == 'panels.as_sub_tab':
|
|
state = self.document.data['general']['panels.as_sub_tab']
|
|
self.as_sub_tab = state
|
|
|
|
if option in ('panels', 'panels.orientation', 'panels.as_sub_tab'):
|
|
ensure_general_options_sanity(self.document.data['general'])
|
|
if count_panels(panels) != len(self.pickers):
|
|
self.create_pickers()
|
|
reset = True
|
|
else:
|
|
self.copy_pickers()
|
|
reset = option in ('panels.orientation', 'panels.as_sub_tab')
|
|
self.create_panels()
|
|
|
|
if option == 'panels.names' and self.as_sub_tab:
|
|
for i, name in enumerate(value):
|
|
self.widget.setTabText(i, name)
|
|
|
|
if option == 'hidden_layers':
|
|
self.layers_menu.hidden_layers = value
|
|
|
|
if reset:
|
|
QtCore.QTimer.singleShot(0, partial(self.reset, force_all=True))
|
|
self.update()
|
|
|
|
def set_auto_center(self, state):
|
|
for picker in self.pickers:
|
|
picker.auto_center = state
|
|
|
|
|
|
class PickerPanelView(QtWidgets.QWidget):
|
|
size_event_triggered = QtCore.Signal(object)
|
|
|
|
def __init__(
|
|
self, document, editable=True, panel=0, layers_menu=None,
|
|
parent=None):
|
|
super(PickerPanelView, self).__init__(parent)
|
|
self._shown = False
|
|
|
|
self.document = document
|
|
self.document.shapes_changed.connect(self.update)
|
|
self.callbacks = []
|
|
self.panel = panel
|
|
self.auto_center = True
|
|
self.editable = editable
|
|
self.interaction_manager = InteractionManager()
|
|
self.viewportmapper = ViewportMapper()
|
|
self.selection_square = SelectionSquare()
|
|
self.layers_menu = layers_menu
|
|
self.setMouseTracking(True)
|
|
self.clicked_shape = None
|
|
self.drag_shapes = []
|
|
|
|
def copy(self):
|
|
self.unregister_callbacks()
|
|
picker = PickerPanelView(
|
|
self.document, self.editable, self.panel, self.layers_menu)
|
|
picker.register_callbacks()
|
|
picker.viewportmapper = self.viewportmapper
|
|
picker.register_callbacks()
|
|
picker.auto_center = self.auto_center
|
|
self.deleteLater()
|
|
return picker
|
|
|
|
def showEvent(self, event):
|
|
if self._shown:
|
|
return super(PickerPanelView, self).showEvent(event)
|
|
self._shown = True
|
|
self.reset(self.size(), selection_only=False)
|
|
|
|
@property
|
|
def zoom_locked(self):
|
|
return self.document.data['general']['panels.zoom_locked'][self.panel]
|
|
|
|
def register_callbacks(self):
|
|
self.unregister_callbacks()
|
|
method = self.sync_with_maya_selection
|
|
cb = om.MEventMessage.addEventCallback('SelectionChanged', method)
|
|
self.callbacks.append(cb)
|
|
|
|
def unregister_callbacks(self):
|
|
for callback in self.callbacks:
|
|
om.MMessage.removeCallback(callback)
|
|
self.callbacks.remove(callback)
|
|
|
|
def sync_with_maya_selection(self, *_):
|
|
if not cmds.optionVar(query=SYNCHRONYZE_SELECTION):
|
|
return
|
|
shapes = self.document.shapes_by_panel[self.panel]
|
|
select_shapes_from_selection(shapes)
|
|
self.update()
|
|
|
|
def visible_shapes(self):
|
|
return [
|
|
s for s in self.document.shapes_by_panel[self.panel] if
|
|
not s.visibility_layer()
|
|
or s.visibility_layer() not in self.layers_menu.hidden_layers]
|
|
|
|
def reset(self, viewsize=None, selection_only=True):
|
|
shapes = [
|
|
s for s in self.visible_shapes() if
|
|
s.options['shape.space'] == 'world' and not
|
|
s.options['shape.ignored_by_focus']]
|
|
shapes_rects = [
|
|
s.bounding_rect() for s in shapes if
|
|
not selection_only or s.selected]
|
|
if not shapes_rects:
|
|
shapes_rects = [s.bounding_rect() for s in shapes]
|
|
if not shapes_rects:
|
|
self.update()
|
|
return
|
|
self.viewportmapper.viewsize = viewsize or self.size()
|
|
rect = get_combined_rects(shapes_rects)
|
|
if self.zoom_locked:
|
|
self.viewportmapper.zoom = 1
|
|
x = rect.center().x() - (self.size().width() / 2)
|
|
y = rect.center().y() - (self.size().height() / 2)
|
|
self.viewportmapper.origin = QtCore.QPointF(x, y)
|
|
else:
|
|
self.viewportmapper.focus(rect)
|
|
self.update()
|
|
|
|
def resizeEvent(self, event):
|
|
if not self.auto_center or event.oldSize() == QtCore.QSize(-1, -1):
|
|
return
|
|
self.adjust_center(event.size(), event.oldSize())
|
|
self.size_event_triggered.emit(event)
|
|
|
|
def adjust_center(self, size, old_size):
|
|
self.viewportmapper.viewsize = self.size()
|
|
size = (size - old_size) / 2
|
|
offset = QtCore.QPointF(size.width(), size.height())
|
|
self.viewportmapper.origin -= offset
|
|
self.update()
|
|
|
|
def enterEvent(self, _):
|
|
self.update()
|
|
|
|
def leaveEvent(self, _):
|
|
for shape in self.visible_shapes():
|
|
shape.hovered = False
|
|
self.update()
|
|
|
|
def mousePressEvent(self, event):
|
|
self.setFocus(QtCore.Qt.MouseFocusReason)
|
|
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)
|
|
|
|
world_cursor = self.viewportmapper.to_units_coords(event.pos())
|
|
shapes = self.visible_shapes()
|
|
self.clicked_shape = detect_hovered_shape(
|
|
shapes=shapes,
|
|
world_cursor=world_cursor.toPoint(),
|
|
screen_cursor=event.pos(),
|
|
viewportmapper=self.viewportmapper)
|
|
|
|
shapes = self.document.shapes_by_panel[self.panel]
|
|
hsh = any(s.hovered for s in shapes)
|
|
self.interaction_manager.update(
|
|
event,
|
|
pressed=True,
|
|
has_shape_hovered=hsh,
|
|
dragging=bool(self.drag_shapes))
|
|
|
|
def mouseDoubleClickEvent(self, event):
|
|
world_cursor = self.viewportmapper.to_units_coords(event.pos())
|
|
shapes = self.visible_shapes()
|
|
clicked_shape = detect_hovered_shape(
|
|
shapes=shapes,
|
|
world_cursor=world_cursor.toPoint(),
|
|
screen_cursor=event.pos(),
|
|
viewportmapper=self.viewportmapper)
|
|
|
|
if not clicked_shape or event.button() != QtCore.Qt.LeftButton:
|
|
return
|
|
|
|
shift = self.interaction_manager.shift_pressed
|
|
ctrl = self.interaction_manager.ctrl_pressed
|
|
selection_mode = get_selection_mode(shift=shift, ctrl=ctrl)
|
|
shapes = self.document.all_children(clicked_shape.options['id'])
|
|
for shape in shapes:
|
|
shape.hovered = True
|
|
select_targets(self.visible_shapes(), selection_mode=selection_mode)
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
shift = self.interaction_manager.shift_pressed
|
|
ctrl = self.interaction_manager.ctrl_pressed
|
|
selection_mode = get_selection_mode(shift=shift, ctrl=ctrl)
|
|
world_cursor = self.viewportmapper.to_units_coords(event.pos())
|
|
zoom = self.interaction_manager.zoom_button_pressed
|
|
shapes = self.visible_shapes()
|
|
|
|
hovered_shape = detect_hovered_shape(
|
|
shapes=shapes,
|
|
world_cursor=world_cursor.toPoint(),
|
|
screen_cursor=event.pos(),
|
|
viewportmapper=self.viewportmapper)
|
|
|
|
interact = (
|
|
self.clicked_shape and
|
|
self.clicked_shape is hovered_shape and
|
|
self.clicked_shape.is_interactive())
|
|
|
|
if zoom and self.interaction_manager.alt_pressed:
|
|
self.release(event)
|
|
return
|
|
|
|
if self.interaction_manager.mode == InteractionManager.DRAGGING:
|
|
self.add_drag_shapes()
|
|
self.release(event)
|
|
return
|
|
|
|
elif self.interaction_manager.mode == InteractionManager.SELECTION and not interact:
|
|
try:
|
|
select_targets(shapes, selection_mode=selection_mode)
|
|
except NameclashError as e:
|
|
warning('Selection Error', str(e), parent=self)
|
|
self.release(event)
|
|
return
|
|
|
|
if not self.clicked_shape:
|
|
if self.interaction_manager.right_click_pressed:
|
|
self.call_context_menu()
|
|
|
|
elif self.clicked_shape is hovered_shape:
|
|
show_context = (
|
|
self.interaction_manager.right_click_pressed and
|
|
not self.clicked_shape.has_right_click_command())
|
|
left_clicked = self.interaction_manager.left_click_pressed
|
|
if show_context:
|
|
self.call_context_menu()
|
|
|
|
elif left_clicked and self.clicked_shape.targets():
|
|
self.clicked_shape.select(selection_mode)
|
|
|
|
if interact:
|
|
button = (
|
|
'left' if self.interaction_manager.left_click_pressed
|
|
else 'right')
|
|
self.clicked_shape.execute(
|
|
button=button,
|
|
ctrl=self.interaction_manager.ctrl_pressed,
|
|
shift=self.interaction_manager.shift_pressed)
|
|
|
|
self.release(event)
|
|
|
|
def add_drag_shapes(self):
|
|
shapes_data = [s.options for s in self.drag_shapes]
|
|
self.document.add_shapes(shapes_data, hierarchize=True)
|
|
self.document.shapes_changed.emit()
|
|
self.document.record_undo()
|
|
self.drag_shapes = []
|
|
|
|
def release(self, event):
|
|
self.interaction_manager.update(event, pressed=False)
|
|
self.selection_square.release()
|
|
self.clicked_shape = None
|
|
self.update()
|
|
|
|
def wheelEvent(self, event):
|
|
# To center the zoom on the mouse, we save a reference mouse position
|
|
# and compare the offset after zoom computation.
|
|
if self.zoom_locked:
|
|
return
|
|
factor = .25 if event.angleDelta().y() > 0 else -.25
|
|
self.zoom(factor, event.pos())
|
|
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 mouseMoveEvent(self, event):
|
|
world_cursor=self.viewportmapper.to_units_coords(event.pos())
|
|
selection_rect = (
|
|
self.selection_square.rect or
|
|
QtCore.QRectF(world_cursor, world_cursor))
|
|
unit_selection_rect = self.viewportmapper.to_units_rect(selection_rect)
|
|
unit_selection_rect = unit_selection_rect.toRect()
|
|
|
|
set_shapes_hovered(
|
|
shapes=self.visible_shapes(),
|
|
world_cursor=world_cursor,
|
|
viewport_cursor=event.pos(),
|
|
selection_rect=unit_selection_rect,
|
|
viewport_selection_rect=selection_rect,
|
|
viewportmapper=self.viewportmapper)
|
|
|
|
if self.interaction_manager.mode == InteractionManager.DRAGGING:
|
|
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)
|
|
return self.update()
|
|
|
|
elif self.interaction_manager.mode == InteractionManager.SELECTION:
|
|
if not self.selection_square.handeling:
|
|
self.selection_square.clicked(event.pos())
|
|
self.selection_square.handle(event.pos())
|
|
return self.update()
|
|
|
|
elif self.interaction_manager.mode == InteractionManager.ZOOMING:
|
|
if self.zoom_locked:
|
|
return self.update()
|
|
offset = self.interaction_manager.mouse_offset(event.pos())
|
|
if offset is not None and self.interaction_manager.zoom_anchor:
|
|
sensitivity = float(cmds.optionVar(query=ZOOM_SENSITIVITY))
|
|
factor = (offset.x() + offset.y()) / sensitivity
|
|
self.zoom(factor, self.interaction_manager.zoom_anchor)
|
|
return self.update()
|
|
|
|
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)
|
|
return self.update()
|
|
self.update()
|
|
|
|
def call_context_menu(self):
|
|
screen_cursor = get_cursor(self)
|
|
world_cursor = self.viewportmapper.to_units_coords(screen_cursor)
|
|
shape = detect_hovered_shape(
|
|
self.visible_shapes(), world_cursor, screen_cursor,
|
|
self.viewportmapper)
|
|
|
|
global_commands = self.document.data['general']['menu_commands']
|
|
context_menu = PickerMenu(global_commands, shape, self.editable)
|
|
|
|
method = partial(self.add_button, world_cursor, button_type=0)
|
|
context_menu.add_single.triggered.connect(method)
|
|
context_menu.add_single.setEnabled(bool(cmds.ls(selection=True)))
|
|
|
|
method = partial(self.add_button, world_cursor, button_type=1)
|
|
context_menu.add_multiple.triggered.connect(method)
|
|
state = len(cmds.ls(selection=True)) > 1
|
|
context_menu.add_multiple.setEnabled(state)
|
|
|
|
method = partial(self.add_button, world_cursor, button_type=2)
|
|
context_menu.add_command.triggered.connect(method)
|
|
|
|
method = partial(self.update_button, self.clicked_shape)
|
|
context_menu.update_button.triggered.connect(method)
|
|
state = bool(self.clicked_shape) and bool(cmds.ls(selection=True))
|
|
context_menu.update_button.setEnabled(state)
|
|
|
|
context_menu.delete_selected.triggered.connect(self.delete_buttons)
|
|
|
|
if self.layers_menu.displayed:
|
|
context_menu.addSeparator()
|
|
context_menu.addMenu(self.layers_menu)
|
|
|
|
action = context_menu.exec_(QtGui.QCursor.pos())
|
|
if isinstance(action, CommandAction):
|
|
if not shape:
|
|
self.execute_menu_command(action.command)
|
|
return
|
|
shape.execute(command=action.command)
|
|
|
|
def execute_menu_command(self, command):
|
|
try:
|
|
execute_code(
|
|
language=command['language'],
|
|
code=command['command'],
|
|
deferred=command['deferred'],
|
|
compact_undo=command['force_compact_undo'])
|
|
except Exception as e:
|
|
import traceback
|
|
print(traceback.format_exc())
|
|
|
|
def update_button(self, shape):
|
|
shape.set_targets(cmds.ls(selection=True))
|
|
self.document.record_undo()
|
|
|
|
def delete_buttons(self):
|
|
selected_shapes = [s for s in self.document.shapes if s.selected]
|
|
self.document.remove_shapes(selected_shapes)
|
|
self.document.record_undo()
|
|
self.document.shapes_changed.emit()
|
|
|
|
def get_quick_options(self):
|
|
|
|
return {
|
|
'bgcolor.normal': cmds.optionVar(query=DEFAULT_BG_COLOR),
|
|
'text.color': cmds.optionVar(query=DEFAULT_TEXT_COLOR),
|
|
'shape.width': cmds.optionVar(query=DEFAULT_WIDTH),
|
|
'shape.height': cmds.optionVar(query=DEFAULT_HEIGHT),
|
|
'text.content': cmds.optionVar(query=DEFAULT_LABEL)}
|
|
|
|
def add_button(self, position, button_type=0):
|
|
"""
|
|
Button types:
|
|
0 = Single button from selection.
|
|
1 = Multiple buttons from selection.
|
|
2 = Command button.
|
|
"""
|
|
targets = cmds.ls(selection=True)
|
|
if not targets and button_type <= 1:
|
|
return warning("Warning", "No targets selected")
|
|
|
|
if button_type == 1:
|
|
overrides = self.get_quick_options()
|
|
overrides['panel'] = self.panel
|
|
shapes = build_multiple_shapes(targets, overrides)
|
|
if not shapes:
|
|
return
|
|
self.drag_shapes = shapes
|
|
return
|
|
|
|
shape_data = deepcopy(BUTTON)
|
|
shape_data['panel'] = self.panel
|
|
shape_data['shape.left'] = position.x()
|
|
shape_data['shape.top'] = position.y()
|
|
shape_data.update(self.get_quick_options())
|
|
if button_type == 0:
|
|
shape_data['action.targets'] = targets
|
|
else:
|
|
text, result = (
|
|
QtWidgets.QInputDialog.getText(self, 'Button text', 'text'))
|
|
if not result:
|
|
return
|
|
shape_data['text.content'] = text
|
|
command = deepcopy(COMMAND)
|
|
languages = ['python', 'mel']
|
|
language = languages[cmds.optionVar(query=LAST_COMMAND_LANGUAGE)]
|
|
command['language'] = language
|
|
dialog = CommandEditorDialog(command)
|
|
if not dialog.exec_():
|
|
return
|
|
command = dialog.command_data()
|
|
index = languages.index(command['language'])
|
|
save_optionvar(LAST_COMMAND_LANGUAGE, index)
|
|
shape_data['action.commands'] = [command]
|
|
|
|
width = max([
|
|
shape_data['shape.width'],
|
|
len(shape_data['text.content']) * 7])
|
|
shape_data['shape.width'] = width
|
|
|
|
self.document.add_shapes([shape_data])
|
|
self.document.record_undo()
|
|
self.document.shapes_changed.emit()
|
|
|
|
def paintEvent(self, _):
|
|
try:
|
|
painter = QtGui.QPainter()
|
|
painter.begin(self)
|
|
# Color background.
|
|
color = self.document.data['general']['panels.colors'][self.panel]
|
|
if color:
|
|
painter.setPen(QtCore.Qt.NoPen)
|
|
painter.setBrush(QtGui.QColor(color))
|
|
painter.drawRect(self.rect())
|
|
|
|
# Color border focus.
|
|
if self.rect().contains(get_cursor(self)):
|
|
draw_picker_focus(painter, self.rect())
|
|
|
|
# List renderable shapes.
|
|
painter.setRenderHints(QtGui.QPainter.Antialiasing)
|
|
hidden_layers = self.layers_menu.hidden_layers
|
|
shapes = [
|
|
shape for shape in self.document.shapes_by_panel[self.panel] if
|
|
not shape.visibility_layer() or
|
|
shape.visibility_layer() not in hidden_layers]
|
|
if self.interaction_manager.left_click_pressed:
|
|
shapes.extend(self.drag_shapes)
|
|
|
|
# Draw shapes and create a mask for arrows shapes.
|
|
cutter = QtGui.QPainterPath()
|
|
cutter.setFillRule(QtCore.Qt.WindingFill)
|
|
for shape in shapes:
|
|
qpath = draw_shape(
|
|
painter, shape,
|
|
force_world_space=False,
|
|
viewportmapper=self.viewportmapper)
|
|
screen_space = shape.options['shape.space'] == 'screen'
|
|
if not shape.options['background'] or screen_space:
|
|
cutter.addPath(qpath)
|
|
|
|
# Draw hierarchy connections.
|
|
connections_path = QtGui.QPainterPath()
|
|
if cmds.optionVar(query=DISPLAY_HIERARCHY_IN_PICKER):
|
|
for shape in shapes:
|
|
if shape.options['shape.space'] == 'screen':
|
|
continue
|
|
for child in shape.options['children']:
|
|
child = self.document.shapes_by_id.get(child)
|
|
hidden = (
|
|
child and
|
|
child.visibility_layer() and
|
|
child.visibility_layer() in hidden_layers)
|
|
screen_space = child.options['shape.space'] == 'screen'
|
|
panel = child.options['panel'] != shape.options['panel']
|
|
if hidden or screen_space or panel:
|
|
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)
|
|
|
|
# Draw Selection square/
|
|
if self.selection_square.rect:
|
|
draw_selection_square(
|
|
painter, self.selection_square.rect)
|
|
|
|
except BaseException as e:
|
|
import traceback
|
|
print(traceback.format_exc())
|
|
print(str(e))
|
|
pass # avoid crash
|
|
# TODO: log the error
|
|
finally:
|
|
painter.end()
|
|
|
|
|
|
class CommandAction(QtWidgets.QAction):
|
|
def __init__(self, command, parent=None):
|
|
super(CommandAction, self).__init__(command['caption'], parent)
|
|
self.command = command
|
|
|
|
|
|
class PickerMenu(QtWidgets.QMenu):
|
|
def __init__(
|
|
self,
|
|
global_commands=None,
|
|
shape=None,
|
|
editable=True,
|
|
parent=None):
|
|
super(PickerMenu, self).__init__(parent)
|
|
|
|
if shape and shape.options['action.menu_commands']:
|
|
for command in shape.options['action.menu_commands']:
|
|
self.addAction(CommandAction(command, self))
|
|
if not global_commands:
|
|
self.addSeparator()
|
|
|
|
if global_commands:
|
|
for command in global_commands:
|
|
self.addAction(CommandAction(command, self))
|
|
self.addSeparator()
|
|
|
|
self.add_single = QtWidgets.QAction('Add single button', self)
|
|
self.add_multiple = QtWidgets.QAction('Add multiple buttons', self)
|
|
self.update_button = QtWidgets.QAction('Update button', self)
|
|
self.add_command = QtWidgets.QAction('Add command', self)
|
|
text = 'Delete selected button(s)'
|
|
self.delete_selected = QtWidgets.QAction(text, self)
|
|
|
|
if editable:
|
|
self.addAction(self.add_single)
|
|
self.addAction(self.add_multiple)
|
|
self.addAction(self.update_button)
|
|
self.addSeparator()
|
|
self.addAction(self.add_command)
|
|
self.addSeparator()
|
|
self.addAction(self.delete_selected)
|
|
|
|
|
|
class VisibilityLayersMenu(QtWidgets.QMenu):
|
|
visibilities_changed = QtCore.Signal()
|
|
def __init__(self, document, parent=None):
|
|
super(VisibilityLayersMenu, self).__init__('Visibility layers', parent)
|
|
self.document = document
|
|
self.document.shapes_changed.connect(self.update_actions)
|
|
self.hidden_layers = document.data['general']['hidden_layers'][:]
|
|
self.update_actions()
|
|
|
|
@property
|
|
def displayed(self):
|
|
return bool(self.document.shapes_by_layer)
|
|
|
|
def update_actions(self):
|
|
self.clear()
|
|
layers = list(self.document.shapes_by_layer)
|
|
action = QtWidgets.QAction('Show all')
|
|
for layer in layers:
|
|
action = QtWidgets.QAction(layer, self)
|
|
action.setCheckable(True)
|
|
action.setChecked(layer not in self.hidden_layers)
|
|
action.toggled.connect(partial(self.set_hidden_layer, layer))
|
|
self.addAction(action)
|
|
|
|
def set_hidden_layer(self, layer, state):
|
|
if state is False and layer not in self.hidden_layers:
|
|
self.hidden_layers.append(layer)
|
|
if state is True and layer in self.hidden_layers:
|
|
self.hidden_layers.remove(layer)
|
|
self.visibilities_changed.emit()
|