452 lines
16 KiB
Python
452 lines
16 KiB
Python
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()
|