This commit is contained in:
2025-11-23 23:31:18 +08:00
parent d60cdc52fd
commit 9f7667a475
710 changed files with 252869 additions and 6 deletions

View 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()