Created
October 15, 2015 17:26
-
-
Save adrientetar/709e303c01cab61d0e0c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From d8c4fd9e783107b4acc54a3c65af7948061825a3 Mon Sep 17 00:00:00 2001 | |
From: =?UTF-8?q?Adrien=20T=C3=A9tar?= <adri-from-59@hotmail.fr> | |
Date: Fri, 9 Oct 2015 16:44:22 +0200 | |
Subject: [PATCH] meta: glyphView tools refactoring (wip) | |
It introduces quite some bugs so I'm not pursuing this further just yet. | |
--- | |
Lib/defconQt/glyphView.py | 980 ++---------------------------------- | |
Lib/defconQt/objects/qtItems.py | 505 +++++++++++++++++++ | |
Lib/defconQt/tools/__init__.py | 0 | |
Lib/defconQt/tools/baseTool.py | 33 ++ | |
Lib/defconQt/tools/knifeTool.py | 69 +++ | |
Lib/defconQt/tools/penTool.py | 184 +++++++ | |
Lib/defconQt/tools/rulerTool.py | 96 ++++ | |
Lib/defconQt/tools/selectionTool.py | 56 +++ | |
Lib/defconQt/util/bezierMath.py | 48 ++ | |
setup.py | 1 + | |
10 files changed, 1045 insertions(+), 927 deletions(-) | |
create mode 100644 Lib/defconQt/objects/qtItems.py | |
create mode 100644 Lib/defconQt/tools/__init__.py | |
create mode 100644 Lib/defconQt/tools/baseTool.py | |
create mode 100644 Lib/defconQt/tools/knifeTool.py | |
create mode 100644 Lib/defconQt/tools/penTool.py | |
create mode 100644 Lib/defconQt/tools/rulerTool.py | |
create mode 100644 Lib/defconQt/tools/selectionTool.py | |
create mode 100644 Lib/defconQt/util/bezierMath.py | |
diff --git a/Lib/defconQt/glyphView.py b/Lib/defconQt/glyphView.py | |
index d2eac03..ef691ac 100644 | |
--- a/Lib/defconQt/glyphView.py | |
+++ b/Lib/defconQt/glyphView.py | |
@@ -1,10 +1,12 @@ | |
-from enum import Enum | |
from math import copysign | |
import pickle | |
from defcon import Anchor, Component | |
from defconQt.objects.defcon import TContour, TGlyph | |
+from defconQt.objects.qtItems import (HandleLineItem, OffCurvePointItem, OnCurvePointItem, | |
+ ResizeHandleItem, AnchorItem, ComponentItem, PixmapItem, VGuidelinesTextItem, roundPosition, | |
+ backgroundColor, bluesColor, fillColor, metricsColor) | |
from defconQt.pens.copySelectionPen import CopySelectionPen | |
-from fontTools.misc import bezierTools | |
+from defconQt.tools import selectionTool, penTool, rulerTool, knifeTool | |
from PyQt5.QtCore import *#QFile, QLineF, QObject, QPointF, QRectF, QSize, Qt | |
from PyQt5.QtGui import *#QBrush, QColor, QImage, QKeySequence, QPainter, QPainterPath, QPixmap, QPen | |
from PyQt5.QtWidgets import *#(QAction, QActionGroup, QApplication, QFileDialog, | |
@@ -144,7 +146,6 @@ class MainGfxWindow(QMainWindow): | |
self.setAttribute(Qt.WA_DeleteOnClose) | |
self.setAttribute(Qt.WA_KeyCompression) | |
- self.view = GlyphView(glyph, self) | |
menuBar = self.menuBar() | |
fileMenu = QMenu("&File", self) | |
@@ -155,27 +156,11 @@ class MainGfxWindow(QMainWindow): | |
glyphMenu.addAction("&Jump", self.changeGlyph, "J") | |
menuBar.addMenu(glyphMenu) | |
- toolBar = QToolBar(self) | |
- toolBar.setMovable(False) | |
- selectionToolButton = toolBar.addAction("Selection", self.view.setSceneSelection) | |
- selectionToolButton.setCheckable(True) | |
- selectionToolButton.setChecked(True) | |
- selectionToolButton.setIcon(QIcon("defconQt/resources/cursor.svg")) | |
- penToolButton = toolBar.addAction("Pen", self.view.setSceneDrawing) | |
- penToolButton.setCheckable(True) | |
- penToolButton.setIcon(QIcon("defconQt/resources/curve.svg")) | |
- rulerToolButton = toolBar.addAction("Ruler", self.view.setSceneRuler) | |
- rulerToolButton.setCheckable(True) | |
- rulerToolButton.setIcon(QIcon("defconQt/resources/ruler.svg")) | |
- knifeToolButton = toolBar.addAction("Knife", self.view.setSceneKnife) | |
- knifeToolButton.setCheckable(True) | |
- knifeToolButton.setIcon(QIcon("defconQt/resources/cut.svg")) | |
- toolsGroup = QActionGroup(self) | |
- toolsGroup.addAction(selectionToolButton) | |
- toolsGroup.addAction(penToolButton) | |
- toolsGroup.addAction(rulerToolButton) | |
- toolsGroup.addAction(knifeToolButton) | |
- self.addToolBar(toolBar) | |
+ self.toolBar = QToolBar(self) | |
+ self.toolBar.setMovable(False) | |
+ self.toolsGroup = QActionGroup(self) | |
+ self.addToolBar(self.toolBar) | |
+ self.view = GlyphView(glyph, self) | |
self.setCentralWidget(self.view) | |
self.setWindowTitle(glyph.name, glyph.getParent()) | |
@@ -212,517 +197,6 @@ class MainGfxWindow(QMainWindow): | |
if font is not None: title = "%s – %s %s" % (title, font.info.familyName, font.info.styleName) | |
super(MainGfxWindow, self).setWindowTitle(title) | |
-def roundPosition(value): | |
- value = value * 10#self._scale | |
- value = round(value) - .5 | |
- value = value * .1#self._inverseScale | |
- return value | |
- | |
-offCurvePointSize = 8#5 | |
-onCurvePointSize = 9#6 | |
-onCurveSmoothPointSize = 10#7 | |
-offWidth = offHeight = roundPosition(offCurvePointSize)# * self._inverseScale) | |
-offHalf = offWidth / 2.0 | |
-onWidth = onHeight = roundPosition(onCurvePointSize)# * self._inverseScale) | |
-onHalf = onWidth / 2.0 | |
-smoothWidth = smoothHeight = roundPosition(onCurveSmoothPointSize)# * self._inverseScale) | |
-smoothHalf = smoothWidth / 2.0 | |
-onCurvePenWidth = 1.5 | |
-offCurvePenWidth = 1.0 | |
- | |
-anchorSize = 11 | |
-anchorWidth = anchorHeight = roundPosition(anchorSize) | |
-anchorHalf = anchorWidth / 2.0 | |
- | |
-bezierHandleColor = QColor.fromRgbF(0, 0, 0, .2) | |
-startPointColor = QColor.fromRgbF(0, 0, 0, .2) | |
-backgroundColor = Qt.white | |
-offCurvePointColor = QColor.fromRgbF(1, 1, 1, 1) | |
-offCurvePointStrokeColor = QColor.fromRgbF(.6, .6, .6, 1) | |
-onCurvePointColor = offCurvePointStrokeColor | |
-onCurvePointStrokeColor = offCurvePointColor | |
-anchorColor = QColor(120, 120, 255) | |
-anchorSelectionColor = Qt.blue | |
-bluesColor = QColor.fromRgbF(.5, .7, 1, .3) | |
-fillColor = QColor(200, 200, 200, 120)#QColor.fromRgbF(0, 0, 0, .4) | |
-componentFillColor = QColor.fromRgbF(0, 0, 0, .4)#QColor.fromRgbF(.2, .2, .3, .4) | |
-metricsColor = QColor(70, 70, 70) | |
-pointSelectionColor = Qt.red | |
- | |
-class SceneTools(Enum): | |
- SelectionTool = 0 | |
- DrawingTool = 1 | |
- RulerTool = 2 | |
- KnifeTool = 3 | |
- | |
-class HandleLineItem(QGraphicsLineItem): | |
- def __init__(self, x1, y1, x2, y2, parent): | |
- super(HandleLineItem, self).__init__(x1, y1, x2, y2, parent) | |
- self.setPen(QPen(bezierHandleColor, 1.0)) | |
- self.setFlag(QGraphicsItem.ItemStacksBehindParent) | |
- | |
-class OffCurvePointItem(QGraphicsEllipseItem): | |
- def __init__(self, x, y, parent=None): | |
- super(OffCurvePointItem, self).__init__(parent) | |
- # since we have a parent, setPos must be relative to it | |
- self.setPointPath() | |
- self.setPos(x, y) # TODO: abstract and use pointX-self.parent().pos().x() | |
- self.setFlag(QGraphicsItem.ItemIsMovable) | |
- self.setFlag(QGraphicsItem.ItemIsSelectable) | |
- self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
- self.setFlag(QGraphicsItem.ItemStacksBehindParent) | |
- | |
- self.setBrush(QBrush(offCurvePointColor)) | |
- self._needsUngrab = False | |
- | |
- def itemChange(self, change, value): | |
- if change == QGraphicsItem.ItemPositionChange: | |
- if self.scene()._integerPlane: | |
- value.setX(round(value.x())) | |
- value.setY(round(value.y())) | |
- if QApplication.keyboardModifiers() & Qt.ShiftModifier \ | |
- and len(self.scene().selectedItems()) == 1: | |
- ax = abs(value.x()) | |
- ay = abs(value.y()) | |
- if ay >= ax * 2: | |
- value.setX(0) | |
- elif ay > ax / 2: | |
- avg = (ax + ay) / 2 | |
- value.setX(copysign(avg, value.x())) | |
- value.setY(copysign(avg, value.y())) | |
- else: | |
- value.setY(0) | |
- elif change == QGraphicsItem.ItemPositionHasChanged: | |
- self.parentItem()._CPMoved(value) | |
- # TODO: consider what to do w offCurves | |
- #elif change == QGraphicsItem.ItemSelectedHasChanged: | |
- # pass#self.parentItem()._CPSelChanged(value) | |
- return value | |
- | |
- def mousePressEvent(self, event): | |
- if not self._needsUngrab and self.x() == 0 and self.y() == 0: | |
- event.ignore() | |
- super(OffCurvePointItem, self).mousePressEvent(event) | |
- | |
- def mouseReleaseEvent(self, event): | |
- super(OffCurvePointItem, self).mouseReleaseEvent(event) | |
- if self._needsUngrab: | |
- self.ungrabMouse() | |
- self._needsUngrab = False | |
- | |
- # http://www.qtfr.org/viewtopic.php?pid=21045#p21045 | |
- def paint(self, painter, option, widget): | |
- #if self.x() == 0 and self.y() == 0: return | |
- newOption = QStyleOptionGraphicsItem(option) | |
- newOption.state = QStyle.State_None | |
- pen = self.pen() | |
- if option.state & QStyle.State_Selected: | |
- pen.setColor(pointSelectionColor) | |
- else: | |
- pen.setColor(offCurvePointStrokeColor) | |
- self.setPen(pen) | |
- super(OffCurvePointItem, self).paint(painter, newOption, widget) | |
- | |
- def setPointPath(self, scale=None): | |
- if scale is None: | |
- scene = self.scene() | |
- if scene is not None: | |
- scale = scene.getViewScale() | |
- else: | |
- scale = 1 | |
- if scale > 4: scale = 4 | |
- elif scale < .4: scale = .4 | |
- self.prepareGeometryChange() | |
- self.setRect(-offHalf/scale, -offHalf/scale, offWidth/scale, offHeight/scale) | |
- self.setPen(QPen(offCurvePointStrokeColor, offCurvePenWidth/scale)) | |
- | |
-class OnCurvePointItem(QGraphicsPathItem): | |
- def __init__(self, x, y, isSmooth, contour, point, scale=1, parent=None): | |
- super(OnCurvePointItem, self).__init__(parent) | |
- self._contour = contour | |
- self._point = point | |
- self._isSmooth = isSmooth | |
- self._posBeforeMove = None | |
- | |
- self.setPointPath(scale) | |
- self.setPos(x, y) | |
- self.setFlag(QGraphicsItem.ItemIsMovable) | |
- self.setFlag(QGraphicsItem.ItemIsSelectable) | |
- self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
- self.setBrush(QBrush(onCurvePointColor)) | |
- | |
- def delete(self, preserveShape=True): | |
- def findNextOnCurve(self, index=0): | |
- for _ in self._contour: | |
- if self._contour[index].segmentType is not None: | |
- break | |
- index = (index+1) % len(self._contour) | |
- return index | |
- | |
- scene = self.scene() | |
- glyph = scene._glyphObject | |
- if len(self._contour.segments) < 2: | |
- glyph.removeContour(self._contour) | |
- else: | |
- ptIndex = self.getPointIndex() | |
- if self._contour.open and ptIndex == 0: | |
- nextOnCurveIndex = findNextOnCurve(self, 1) | |
- self._contour._points = self._contour[nextOnCurveIndex:] | |
- self._contour[0].segmentType = "move" | |
- self._contour.dirty = True | |
- else: | |
- # Using preserveShape at the edge of an open contour will traceback | |
- if ptIndex == len(self._contour): preserveShape = False | |
- self._contour.removeSegment(self.getSegmentIndex(), preserveShape) | |
- nextOnCurveIndex = findNextOnCurve(self) | |
- self._contour.setStartPoint(nextOnCurveIndex) | |
- # This object will be removed from scene by notification mechanism | |
- | |
- def setPointPath(self, scale=None): | |
- path = QPainterPath() | |
- if scale is None: | |
- scene = self.scene() | |
- if scene is not None: | |
- scale = scene.getViewScale() | |
- else: | |
- scale = 1 | |
- if scale > 4: scale = 4 | |
- elif scale < .4: scale = .4 | |
- if self._isSmooth: | |
- path.addEllipse(-smoothHalf/scale, -smoothHalf/scale, smoothWidth/scale, smoothHeight/scale) | |
- else: | |
- path.addRect(-onHalf/scale, -onHalf/scale, onWidth/scale, onHeight/scale) | |
- self.prepareGeometryChange() | |
- self.setPath(path) | |
- self.setPen(QPen(onCurvePointStrokeColor, onCurvePenWidth/scale)) | |
- | |
- def getPointIndex(self): | |
- return self._contour.index(self._point) | |
- | |
- def getSegmentIndex(self): | |
- # closed contour cycles and so the "previous" segment goes to current point | |
- index = 0 if self._contour.open else -1 | |
- for pt in self._contour: | |
- if pt == self._point: break | |
- if pt.segmentType is not None: index += 1 | |
- return index % len(self._contour.segments) | |
- | |
- def _CPMoved(self, newValue): | |
- pointIndex = self.getPointIndex() | |
- children = self.childItems() | |
- # nodes are stored after lines (for stacking order) | |
- if children[1].isSelected(): | |
- selected = 1 | |
- propagate = 3 | |
- else: | |
- selected = 3 | |
- propagate = 1 | |
- curValue = children[selected].pos() | |
- line = children[selected-1].line() | |
- children[selected-1].setLine(line.x1(), line.y1(), newValue.x(), newValue.y()) | |
- | |
- if not len(children) > 4: | |
- elemIndex = pointIndex-2+selected | |
- self._contour[elemIndex].x = self.pos().x()+newValue.x() | |
- self._contour[elemIndex].y = self.pos().y()+newValue.y() | |
- if not (self._isSmooth and children[propagate].isVisible()): | |
- self.setShallowDirty() | |
- return | |
- if children[selected]._needsUngrab: | |
- targetLen = children[selected-1].line().length()*2 | |
- else: | |
- targetLen = children[selected-1].line().length()+children[propagate-1].line().length() | |
- tmpLine = QLineF(newValue, QPointF(0, 0)) | |
- tmpLine.setLength(targetLen) | |
- children[propagate].setFlag(QGraphicsItem.ItemSendsGeometryChanges, False) | |
- children[propagate].setPos(tmpLine.x2(), tmpLine.y2()) | |
- children[propagate].setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
- children[propagate-1].setLine(line.x1(), line.y1(), tmpLine.x2(), tmpLine.y2()) | |
- propagateInContour = pointIndex-2+propagate | |
- self._contour[propagateInContour].x = self.pos().x()+tmpLine.x2() | |
- self._contour[propagateInContour].y = self.pos().y()+tmpLine.y2() | |
- self.setShallowDirty() | |
- | |
- def itemChange(self, change, value): | |
- if change == QGraphicsItem.ItemPositionChange: | |
- if self.scene()._integerPlane: | |
- value.setX(round(value.x())) | |
- value.setY(round(value.y())) | |
- elif change == QGraphicsItem.ItemPositionHasChanged: | |
- # have a look at defcon FuzzyNumber as well | |
- pointIndex = self.getPointIndex() | |
- self._contour[pointIndex].x = self.pos().x() | |
- self._contour[pointIndex].y = self.pos().y() | |
- | |
- children = self.childItems() | |
- if children[1].isVisible(): | |
- prevPos = children[1].pos() | |
- self._contour[pointIndex-1].x = self.pos().x()+prevPos.x() | |
- self._contour[pointIndex-1].y = self.pos().y()+prevPos.y() | |
- if children[3].isVisible(): | |
- nextPos = children[3].pos() | |
- self._contour[pointIndex+1].x = self.pos().x()+nextPos.x() | |
- self._contour[pointIndex+1].y = self.pos().y()+nextPos.y() | |
- self.setShallowDirty() | |
- elif change == QGraphicsItem.ItemSelectedHasChanged: | |
- self._point.selected = value | |
- return value | |
- | |
- def setShallowDirty(self): | |
- scene = self.scene() | |
- scene._blocked = True | |
- self._contour.dirty = True | |
- scene._blocked = False | |
- | |
- def mouseMoveEvent(self, event): | |
- modifiers = event.modifiers() | |
- children = self.childItems() | |
- # Ctrl: get and move prevCP, Alt: nextCP | |
- if modifiers & Qt.ControlModifier and children[1].x() == 0 and children[1].y() == 0: | |
- i, o = 1, 3 | |
- elif modifiers & Qt.AltModifier and children[3].x() == 0 and children[3].y() == 0: | |
- i, o = 3, 1 | |
- elif not (modifiers & Qt.ControlModifier or modifiers & Qt.AltModifier): | |
- super(OnCurvePointItem, self).mouseMoveEvent(event) | |
- return | |
- else: # eat the event if we are not going to yield an offCP | |
- event.accept() | |
- return | |
- ptIndex = self.getPointIndex() | |
- scene = self.scene() | |
- scene._blocked = True | |
- # if we have line segment, insert offCurve points | |
- insertIndex = (ptIndex+(i-1)//2) % len(self._contour) | |
- if self._contour[insertIndex].segmentType == "line": | |
- nextToCP = self._contour[(ptIndex-2+i) % len(self._contour)] | |
- assert(nextToCP.segmentType is not None) | |
- self._contour[insertIndex].segmentType = "curve" | |
- if i == 1: | |
- first, second = (self._point.x, self._point.y), (nextToCP.x, nextToCP.y) | |
- else: | |
- first, second = (nextToCP.x, nextToCP.y), (self._point.x, self._point.y) | |
- self._contour.insertPoint(insertIndex, self._contour._pointClass(first)) | |
- self._contour.insertPoint(insertIndex, self._contour._pointClass(second)) | |
- children[i].setVisible(True) | |
- # TODO: need a list of items to make this efficient | |
- scene.getItemForPoint(nextToCP).childItems()[o].setVisible(True) | |
- # release current onCurve | |
- scene.sendEvent(self, QEvent(QEvent.MouseButtonRelease)) | |
- scene.mouseGrabberItem().ungrabMouse() | |
- self.setSelected(False) | |
- self.setIsSmooth(False) | |
- children[i]._needsUngrab = True | |
- scene.sendEvent(children[i], QEvent(QEvent.MouseButtonPress)) | |
- children[i].setSelected(True) | |
- children[i].grabMouse() | |
- scene._blocked = False | |
- event.accept() | |
- | |
- def mouseDoubleClickEvent(self, event): | |
- view = self.scene().views()[0] # XXX: meh, maybe refactor doubleClick event into the scene? | |
- if view._currentTool == SceneTools.RulerTool or view._currentTool == SceneTools.KnifeTool: | |
- return | |
- self.setIsSmooth(not self._isSmooth) | |
- | |
- def setIsSmooth(self, isSmooth): | |
- self._isSmooth = isSmooth | |
- self._point.smooth = self._isSmooth | |
- self.setShallowDirty() | |
- self.setPointPath() | |
- | |
- # http://www.qtfr.org/viewtopic.php?pid=21045#p21045 | |
- def paint(self, painter, option, widget): | |
- newOption = QStyleOptionGraphicsItem(option) | |
- newOption.state = QStyle.State_None | |
- pen = self.pen() | |
- if option.state & QStyle.State_Selected: | |
- pen.setColor(pointSelectionColor) | |
- else: | |
- pen.setColor(onCurvePointStrokeColor) | |
- self.setPen(pen) | |
- super(OnCurvePointItem, self).paint(painter, newOption, widget) | |
- | |
-class AnchorItem(QGraphicsPathItem): | |
- def __init__(self, anchor, scale=1, parent=None): | |
- super(AnchorItem, self).__init__(parent) | |
- self._anchor = anchor | |
- | |
- textItem = QGraphicsSimpleTextItem(self._anchor.name, parent=self) | |
- font = QFont() | |
- font.setPointSize(9) | |
- textItem.setFont(font) | |
- textItem.setFlag(QGraphicsItem.ItemIgnoresTransformations) | |
- self.setPointPath(scale) | |
- self.setPos(self._anchor.x, self._anchor.y) | |
- self.setFlag(QGraphicsItem.ItemIsMovable) | |
- self.setFlag(QGraphicsItem.ItemIsSelectable) | |
- self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
- self.setBrush(QBrush(anchorColor)) | |
- self.setPen(QPen(Qt.NoPen)) | |
- | |
- def delete(self): | |
- glyph = self._anchor.getParent() | |
- glyph.removeAnchor(self._anchor) | |
- | |
- def itemChange(self, change, value): | |
- if change == QGraphicsItem.ItemPositionChange: | |
- if self.scene()._integerPlane: | |
- value.setX(round(value.x())) | |
- value.setY(round(value.y())) | |
- elif change == QGraphicsItem.ItemPositionHasChanged: | |
- x = self.pos().x() | |
- y = self.pos().y() | |
- scene = self.scene() | |
- scene._blocked = True | |
- self._anchor.x = x | |
- self._anchor.y = y | |
- scene._blocked = False | |
- return value | |
- | |
- def setPointPath(self, scale=None): | |
- path = QPainterPath() | |
- if scale is None: | |
- scene = self.scene() | |
- if scene is not None: | |
- scale = scene.getViewScale() | |
- else: | |
- scale = 1 | |
- if scale > 4: scale = 4 | |
- elif scale < .4: scale = .4 | |
- | |
- path.moveTo(-anchorHalf/scale, 0) | |
- path.lineTo(0, anchorHalf/scale) | |
- path.lineTo(anchorHalf/scale, 0) | |
- path.lineTo(0, -anchorHalf/scale) | |
- path.closeSubpath() | |
- | |
- self.prepareGeometryChange() | |
- self.setPath(path) | |
- textItem = self.childItems()[0] | |
- textItem.setPos(anchorHalf/scale, textItem.boundingRect().height()/2) | |
- | |
- # http://www.qtfr.org/viewtopic.php?pid=21045#p21045 | |
- def paint(self, painter, option, widget): | |
- newOption = QStyleOptionGraphicsItem(option) | |
- newOption.state = QStyle.State_None | |
- pen = self.pen() | |
- if option.state & QStyle.State_Selected: | |
- self.setBrush(anchorSelectionColor) | |
- else: | |
- self.setBrush(anchorColor) | |
- super(AnchorItem, self).paint(painter, newOption, widget) | |
- | |
-class ComponentItem(QGraphicsPathItem): | |
- def __init__(self, path, component, parent=None): | |
- super(ComponentItem, self).__init__(path, parent) | |
- self._component = component | |
- self.setTransform(QTransform(*component.transformation)) | |
- self.setBrush(QBrush(componentFillColor)) | |
- self.setFlag(QGraphicsItem.ItemIsMovable) | |
- self.setFlag(QGraphicsItem.ItemIsSelectable) | |
- self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
- | |
- def delete(self): | |
- glyph = self._component.getParent() | |
- glyph.removeComponent(self._component) | |
- | |
- def itemChange(self, change, value): | |
- if change == QGraphicsItem.ItemPositionChange: | |
- if self.scene()._integerPlane: | |
- value.setX(round(value.x())) | |
- value.setY(round(value.y())) | |
- elif change == QGraphicsItem.ItemPositionHasChanged: | |
- t = self._component.transformation | |
- x = self.pos().x() | |
- y = self.pos().y() | |
- scene = self.scene() | |
- scene._blocked = True | |
- self._component.transformation = (t[0], t[1], t[2], t[3], x, y) | |
- scene._blocked = False | |
- return value | |
- | |
-class VGuidelinesTextItem(QGraphicsSimpleTextItem): | |
- def __init__(self, text, font, parent=None): | |
- super(VGuidelinesTextItem, self).__init__(text, parent) | |
- self.setBrush(metricsColor) | |
- self.setFlag(QGraphicsItem.ItemIgnoresTransformations) | |
- self.setFont(font) | |
- | |
-class ResizeHandleItem(QGraphicsRectItem): | |
- def __init__(self, parent=None): | |
- super(QGraphicsRectItem, self).__init__(parent) | |
- self.setPointPath() | |
- self.setBrush(QBrush(QColor(60, 60, 60))) | |
- self.setPen(QPen(Qt.NoPen)) | |
- self.setFlag(QGraphicsItem.ItemIgnoresParentOpacity) | |
- self.setFlag(QGraphicsItem.ItemIsMovable) | |
- #self.setFlag(QGraphicsItem.ItemIsSelectable) | |
- self.setCursor(Qt.SizeFDiagCursor) | |
- | |
- rect = self.parentItem().boundingRect() | |
- self.setPos(rect.width(), rect.height()) | |
- | |
- def itemChange(self, change, value): | |
- if change == QGraphicsItem.ItemSelectedChange: | |
- if not value: self.setVisible(value) | |
- return value | |
- | |
- def mouseMoveEvent(self, event): | |
- self.parentItem()._pixmapGeometryChanged(event) | |
- | |
- def setPointPath(self, scale=None): | |
- if scale is None: | |
- scene = self.scene() | |
- if scene is not None: | |
- scale = scene.getViewScale() | |
- else: | |
- scale = 1 | |
- if scale > 4: scale = 4 | |
- self.prepareGeometryChange() | |
- self.setRect(-onHalf/scale, -onHalf/scale, onWidth/scale, onHeight/scale) | |
- | |
-class PixmapItem(QGraphicsPixmapItem): | |
- def __init__(self, x, y, pixmap, parent=None): | |
- super(QGraphicsPixmapItem, self).__init__(pixmap, parent) | |
- self.setPos(x, y) | |
- self.setFlag(QGraphicsItem.ItemIsMovable) | |
- self.setFlag(QGraphicsItem.ItemIsSelectable) | |
- self.setTransform(QTransform().fromScale(1, -1)) | |
- self.setOpacity(.5) | |
- self.setZValue(-1) | |
- | |
- rect = self.boundingRect() | |
- self._rWidth = rect.width() | |
- self._rHeight = rect.height() | |
- handle = ResizeHandleItem(self) | |
- handle.setVisible(False) | |
- | |
- def _pixmapGeometryChanged(self, event): | |
- modifiers = event.modifiers() | |
- pos = event.scenePos() | |
- if modifiers & Qt.ControlModifier: | |
- # rotate | |
- refLine = QLineF(self.x(), self.y(), self.x()+self._rWidth, self.y()-self._rHeight) | |
- curLine = QLineF(self.x(), self.y(), pos.x(), pos.y()) | |
- self.setRotation(refLine.angleTo(curLine)) | |
- else: | |
- # scale | |
- dy = (pos.y() - self.y()) / self._rHeight | |
- if modifiers & Qt.ShiftModifier: | |
- # keep original aspect ratio | |
- dx = -dy | |
- else: | |
- dx = (pos.x() - self.x()) / self._rWidth | |
- self.setTransform(QTransform().fromScale(dx, dy)) | |
- event.accept() | |
- | |
- def itemChange(self, change, value): | |
- if change == QGraphicsItem.ItemSelectedChange: | |
- children = self.childItems() | |
- if not children[0].isUnderMouse(): | |
- children[0].setVisible(value) | |
- return value | |
- | |
class GlyphScene(QGraphicsScene): | |
def __init__(self, parent): | |
super(GlyphScene, self).__init__(parent) | |
@@ -893,387 +367,24 @@ class GlyphScene(QGraphicsScene): | |
super(GlyphScene, self).keyReleaseEvent(event) | |
def mousePressEvent(self, event): | |
- if event.button() == Qt.RightButton: | |
- self._rightClickPos = event.scenePos() | |
view = self.views()[0] | |
- touched = self.itemAt(event.scenePos(), view.transform()) | |
- if view._currentTool == SceneTools.RulerTool: | |
- self.rulerMousePress(event) | |
- return | |
- else: | |
- data = self._glyphObject.serialize() | |
- self._dataForUndo.append(data) | |
- self._dataForRedo = [] | |
- if view._currentTool == SceneTools.KnifeTool: | |
- self.knifeMousePress(event) | |
- return | |
- elif view._currentTool == SceneTools.SelectionTool: | |
- super(GlyphScene, self).mousePressEvent(event) | |
- return | |
- self._blocked = True | |
- forceSelect = False | |
- sel = self.selectedItems() | |
- x, y = event.scenePos().x(), event.scenePos().y() | |
- if self._integerPlane: | |
- x, y = round(x), round(y) | |
- # XXX: not sure why isinstance does not work here | |
- if len(sel) == 1: | |
- isLastOnCurve = type(sel[0]) is OnCurvePointItem and sel[0]._contour.open and \ | |
- sel[0].getPointIndex() == len(sel[0]._contour)-1 | |
- # TODO: reimplement convenience methods in OffCurvePointItem | |
- isLastOffCurve = type(sel[0]) is OffCurvePointItem and sel[0].parentItem()._contour.open and \ | |
- sel[0].parentItem().getPointIndex()+1 == len(sel[0].parentItem()._contour)-1 | |
- if len(sel) == 1 and (isLastOffCurve or isLastOnCurve): | |
- if isLastOnCurve: | |
- lastContour = sel[0]._contour | |
- else: | |
- lastContour = sel[0].parentItem()._contour | |
- if (touched and isinstance(touched, OnCurvePointItem)) and touched.getPointIndex() == 0 \ | |
- and lastContour == touched._contour and len(lastContour) > 1: | |
- # Changing the first point from move to line/curve will cycle and so close the contour | |
- if isLastOffCurve: | |
- lastContour.addPoint((x,y)) | |
- lastContour[0].segmentType = "curve" | |
- touched.childItems()[1].setVisible(True) | |
- else: | |
- lastContour[0].segmentType = "line" | |
- elif touched and isinstance(touched, OnCurvePointItem): | |
- super(GlyphScene, self).mousePressEvent(event) | |
- return | |
- else: | |
- if QApplication.keyboardModifiers() & Qt.ShiftModifier: | |
- forceSelect = True | |
- if isLastOnCurve: | |
- refx = sel[0].x() | |
- refy = sel[0].y() | |
- else: | |
- refx = sel[0].parentItem().x() | |
- refy = sel[0].parentItem().y() | |
- if abs(x-refx) > abs(y-refy): y = copysign(refy, y) | |
- else: x = copysign(refx, x) | |
- if isLastOffCurve: | |
- lastContour.addPoint((x,y)) | |
- lastContour.addPoint((x,y), "curve") | |
- else: | |
- lastContour.addPoint((x,y), "line") | |
- item = OnCurvePointItem(x, y, False, lastContour, lastContour[-1], self.getViewScale()) | |
- self.addItem(item) | |
- for _ in range(2): | |
- lineObj = HandleLineItem(0, 0, 0, 0, item) | |
- CPObject = OffCurvePointItem(0, 0, item) | |
- CPObject.setVisible(False) | |
- if isLastOffCurve: | |
- item.childItems()[1].setVisible(True) | |
- lastContour.dirty = True | |
- self._editing = True | |
- elif not (touched and isinstance(touched, OnCurvePointItem)): | |
- nextC = TContour() | |
- self._glyphObject.appendContour(nextC) | |
- nextC.addPoint((x,y), "move") | |
- | |
- item = OnCurvePointItem(x, y, False, self._glyphObject[-1], self._glyphObject[-1][-1], self.getViewScale()) | |
- self.addItem(item) | |
- for _ in range(2): | |
- lineObj = HandleLineItem(0, 0, 0, 0, item) | |
- CPObject = OffCurvePointItem(0, 0, item) | |
- CPObject.setVisible(False) | |
- self._editing = True | |
- self._blocked = False | |
- super(GlyphScene, self).mousePressEvent(event) | |
- # Since shift clamps, we might be missing the point in mousePressEvent | |
- if forceSelect: item.setSelected(True) | |
+ CurrentTool = view._currentTool | |
+ CurrentTool.mousePressEvent(self, event) | |
def mouseMoveEvent(self, event): | |
- if self._editing is True: | |
- sel = self.selectedItems() | |
- if len(sel) == 1: | |
- if isinstance(sel[0], OnCurvePointItem) and (event.scenePos() - sel[0].pos()).manhattanLength() >= 2: | |
- mouseGrabberItem = self.mouseGrabberItem() | |
- # If we drawn an onCurve w Shift and we're not touching the item, we wont have | |
- # a mouse grabber (anyways), return early here. | |
- if mouseGrabberItem is None: | |
- event.accept() | |
- return | |
- self._blocked = True | |
- if len(sel[0]._contour) < 2: | |
- # release current onCurve | |
- self.sendEvent(sel[0], QEvent(QEvent.MouseButtonRelease)) | |
- mouseGrabberItem.ungrabMouse() | |
- sel[0].setSelected(False) | |
- # append an offCurve point and start moving it | |
- sel[0]._contour.addPoint((event.scenePos().x(), event.scenePos().y())) | |
- nextCP = sel[0].childItems()[3] | |
- nextCP.setVisible(True) | |
- nextCP._needsUngrab = True | |
- #nextCP.setSelected(True) | |
- self.sendEvent(nextCP, QEvent(QEvent.MouseButtonPress)) | |
- nextCP.grabMouse() | |
- else: | |
- # release current onCurve, delete from contour | |
- self.sendEvent(sel[0], QEvent(QEvent.MouseButtonRelease)) | |
- mouseGrabberItem.ungrabMouse() | |
- sel[0].setSelected(False) | |
- | |
- # construct a curve segment to the current point if there is not one | |
- onCurve = sel[0]._point | |
- if not onCurve.segmentType == "curve": | |
- # remove the last onCurve | |
- sel[0]._contour.removePoint(onCurve) | |
- prev = sel[0]._contour[-1] | |
- self.getItemForPoint(prev).childItems()[3].setVisible(True) | |
- # add a zero-length offCurve to the previous point | |
- sel[0]._contour.addPoint((prev.x, prev.y)) | |
- # add prevOffCurve and activate | |
- sel[0]._contour.addPoint((sel[0].x(), sel[0].y())) | |
- sel[0].childItems()[1].setVisible(True) | |
- # add back current onCurve as a curve point | |
- sel[0]._contour.addPoint((onCurve.x, onCurve.y), "curve") | |
- sel[0]._point = sel[0]._contour[-1] | |
- if not QApplication.keyboardModifiers() & Qt.AltModifier: | |
- sel[0]._point.smooth = True | |
- sel[0]._isSmooth = True | |
- sel[0].setPointPath() | |
- if sel[0].getPointIndex() == 0: | |
- # we're probably dealing with the first point that we looped. | |
- # preserve nextCP whatsoever. | |
- lineObj = HandleLineItem(0, 0, 0, 0, sel[0]) | |
- nextCP = OffCurvePointItem(0, 0, sel[0]) | |
- # now we have l1, p1, l2, p2, l3, p3 | |
- l2 = sel[0].childItems()[2] | |
- lineObj.stackBefore(l2) | |
- nextCP.stackBefore(l2) | |
- else: | |
- # add last offCurve | |
- sel[0]._contour.addPoint((sel[0].x(), sel[0].y())) | |
- nextCP = sel[0].childItems()[3] | |
- nextCP._needsUngrab = True | |
- nextCP.setVisible(True) | |
- #nextCP.setSelected(True) | |
- self.sendEvent(nextCP, QEvent(QEvent.MouseButtonPress)) | |
- nextCP.grabMouse() | |
- self._blocked = False | |
- self._editing = None | |
- super(GlyphScene, self).mouseMoveEvent(event) | |
- else: | |
- # eat the event | |
- event.accept() | |
- else: | |
- currentTool = self.views()[0]._currentTool | |
- if currentTool == SceneTools.RulerTool: | |
- self.rulerMouseMove(event) | |
- return | |
- elif currentTool == SceneTools.KnifeTool: | |
- self.knifeMouseMove(event) | |
- return | |
- items = self.items(event.scenePos()) | |
- # XXX: we must cater w mouse tracking | |
- # we dont need isSelected() once its rid | |
- if len(items) > 1 and isinstance(items[0], OnCurvePointItem) and \ | |
- isinstance(items[1], OffCurvePointItem) and items[1].isSelected(): | |
- items[1].setPos(0, 0) | |
- else: | |
- super(GlyphScene, self).mouseMoveEvent(event) | |
+ view = self.views()[0] | |
+ CurrentTool = view._currentTool | |
+ CurrentTool.mouseMoveEvent(self, event) | |
def mouseReleaseEvent(self, event): | |
- self._editing = False | |
- currentTool = self.views()[0]._currentTool | |
- if currentTool == SceneTools.DrawingTool: | |
- # cleanup extra point elements if we dealt w curved first point | |
- touched = self.itemAt(event.scenePos(), self.views()[0].transform()) | |
- if touched and isinstance(touched, OffCurvePointItem): | |
- onCurve = touched.parentItem() | |
- children = onCurve.childItems() | |
- if len(children) > 4: | |
- # l1, p1, l3, p3, l2, p2 | |
- children[3].prepareGeometryChange() | |
- self.removeItem(children[3]) | |
- children[2].prepareGeometryChange() | |
- self.removeItem(children[2]) | |
- | |
- onCurve._isSmooth = False | |
- onCurve.setPointPath() | |
- onCurve._point.smooth = False | |
- elif currentTool == SceneTools.RulerTool: | |
- self.rulerMouseRelease(event) | |
- elif currentTool == SceneTools.KnifeTool: | |
- self.knifeMouseRelease(event) | |
- super(GlyphScene, self).mouseReleaseEvent(event) | |
- | |
- def rulerMousePress(self, event): | |
- touched = self.itemAt(event.scenePos(), self.views()[0].transform()) | |
- if touched is not None and isinstance(touched, OnCurvePointItem) or \ | |
- isinstance(touched, OffCurvePointItem): | |
- x, y = touched.scenePos().x(), touched.scenePos().y() | |
- else: | |
- x, y = event.scenePos().x(), event.scenePos().y() | |
- if self._integerPlane: | |
- x, y = round(x), round(y) | |
- if self._cachedRuler is not None: | |
- self.removeItem(self._cachedRuler) | |
- self._cachedRuler = None | |
- path = QPainterPath() | |
- path.moveTo(x, y) | |
- path.lineTo(x+1, y) | |
- path.lineTo(x+1, y+1) | |
- path.closeSubpath() | |
- self._rulerObject = self.addPath(path) | |
- textItem = QGraphicsSimpleTextItem("0", self._rulerObject) | |
- font = self.font() | |
- font.setPointSize(9) | |
- textItem.setFont(font) | |
- textItem.setFlag(QGraphicsItem.ItemIgnoresTransformations) | |
- textItem.setPos(x, y + textItem.boundingRect().height()) | |
- event.accept() | |
- | |
- def rulerMouseMove(self, event): | |
- # XXX: shouldnt have to do this, it seems mouseTracking is wrongly activated | |
- if self._rulerObject is None: return | |
- touched = self.itemAt(event.scenePos(), self.views()[0].transform()) | |
- if touched is not None and isinstance(touched, OnCurvePointItem) or \ | |
- isinstance(touched, OffCurvePointItem): | |
- x, y = touched.scenePos().x(), touched.scenePos().y() | |
- else: | |
- # TODO: 45deg clamp w ShiftModifier | |
- # maybe make a function for that + other occurences... | |
- x, y = event.scenePos().x(), event.scenePos().y() | |
- if self._integerPlane: | |
- x, y = round(x), round(y) | |
- path = self._rulerObject.path() | |
- baseElem = path.elementAt(0) | |
- path.setElementPositionAt(1, x, baseElem.y) | |
- path.setElementPositionAt(2, x, y) | |
- path.setElementPositionAt(3, baseElem.x, baseElem.y) | |
- self._rulerObject.setPath(path) | |
- textItem = self._rulerObject.childItems()[0] | |
- line = QLineF(baseElem.x, baseElem.y, x, y) | |
- l = line.length() | |
- # XXX: angle() doesnt go by trigonometric direction. Weird. | |
- # TODO: maybe split in positive/negative 180s (ff) | |
- a = 360 - line.angle() | |
- line.setP2(QPointF(x, baseElem.y)) | |
- h = line.length() | |
- line.setP1(QPointF(x, y)) | |
- v = line.length() | |
- text = "%d\n↔ %d\n↕ %d\nα %dº" % (l, h, v, a) | |
- textItem.setText(text) | |
- dx = x - baseElem.x | |
- if dx >= 0: px = x | |
- else: px = x - textItem.boundingRect().width() | |
- dy = y - baseElem.y | |
- if dy > 0: py = baseElem.y | |
- else: py = baseElem.y + textItem.boundingRect().height() | |
- textItem.setPos(px, py) | |
- event.accept() | |
- | |
- def rulerMouseRelease(self, event): | |
- textItem = self._rulerObject.childItems()[0] | |
- if textItem.text() == "0": | |
- # delete no-op ruler | |
- self.removeItem(self._rulerObject) | |
- self._rulerObject = None | |
- else: | |
- self._cachedRuler = self._rulerObject | |
- self._rulerObject = None | |
- event.accept() | |
- | |
- def knifeMousePress(self, event): | |
- scenePos = event.scenePos() | |
- x, y = scenePos.x(), scenePos.y() | |
- self._knifeLine = self.addLine(x, y, x, y) | |
- event.accept() | |
- | |
- """ | |
- Computes intersection between a cubic spline and a line segment. | |
- Adapted from: https://www.particleincell.com/2013/cubic-line-intersection/ | |
- | |
- Takes four defcon points describing curve and four scalars describing line | |
- parameters. | |
- """ | |
- def computeIntersections(self, p1, p2, p3, p4, x1, y1, x2, y2): | |
- bx, by = x1 - x2, y2 - y1 | |
- m = x1*(y1-y2) + y1*(x2-x1) | |
- a, b, c, d = bezierTools.calcCubicParameters((p1.x, p1.y), (p2.x, p2.y), | |
- (p3.x, p3.y), (p4.x, p4.y)) | |
- | |
- pc0 = by*a[0] + bx*a[1] | |
- pc1 = by*b[0] + bx*b[1] | |
- pc2 = by*c[0] + bx*c[1] | |
- pc3 = by*d[0] + bx*d[1] + m | |
- r = bezierTools.solveCubic(pc0, pc1, pc2, pc3) | |
- | |
- sol = [] | |
- for t in r: | |
- s0 = a[0]*t**3 + b[0]*t**2 + c[0]*t + d[0] | |
- s1 = a[1]*t**3 + b[1]*t**2 + c[1]*t + d[1] | |
- if (x2-x1) != 0: | |
- s = (s0-x1) / (x2-x1) | |
- else: | |
- s = (s1-y1) / (y2-y1) | |
- if not (t < 0 or t > 1 or s < 0 or s > 1): | |
- sol.append((s0, s1, t)) | |
- return sol | |
- | |
- """ | |
- G. Bach, http://stackoverflow.com/a/1968345 | |
- """ | |
- def lineIntersection(self, x1, y1, x2, y2, x3, y3, x4, y4): | |
- Bx_Ax = x2 - x1 | |
- By_Ay = y2 - y1 | |
- Dx_Cx = x4 - x3 | |
- Dy_Cy = y4 - y3 | |
- determinant = (-Dx_Cx * By_Ay + Bx_Ax * Dy_Cy) | |
- if abs(determinant) < 1e-20: return [] | |
- s = (-By_Ay * (x1 - x3) + Bx_Ax * (y1 - y3)) / determinant | |
- t = ( Dx_Cx * (y1 - y3) - Dy_Cy * (x1 - x3)) / determinant | |
- if s >= 0 and s <= 1 and t >= 0 and t <= 1: | |
- return [(x1 + (t * Bx_Ax), y1 + (t * By_Ay), t)] | |
- return [] | |
- | |
- def knifeMouseMove(self, event): | |
- # XXX: shouldnt have to do this, it seems mouseTracking is wrongly activated | |
- if self._knifeLine is None: return | |
- for dot in self._knifeDots: | |
- self.removeItem(dot) | |
- self._knifeDots = [] | |
- scenePos = event.scenePos() | |
- x, y = scenePos.x(), scenePos.y() | |
- line = self._knifeLine.line() | |
- line.setP2(QPointF(x, y)) | |
- # XXX: not nice | |
- glyph = self.views()[0]._glyph | |
- self._cachedIntersections = [] | |
- for contour in glyph: | |
- segments = contour.segments | |
- for index, seg in enumerate(segments): | |
- prev = segments[index-1][-1] | |
- if len(seg) == 3: | |
- i = self.computeIntersections(prev, seg[0], seg[1], seg[2], line.x1(), line.y1(), x, y) | |
- else: | |
- i = self.lineIntersection(prev.x, prev.y, seg[0].x, seg[0].y, line.x1(), line.y1(), x, y) | |
- for pt in i: | |
- scale = self.getViewScale() | |
- item = self.addEllipse(-offHalf/scale, -offHalf/scale, offWidth/scale, offHeight/scale) | |
- item.setPos(pt[0], pt[1]) | |
- self._cachedIntersections.append((contour, index, pt[2])) | |
- self._knifeDots.append(item) | |
- self._knifeLine.setLine(line) | |
- event.accept() | |
+ view = self.views()[0] | |
+ CurrentTool = view._currentTool | |
+ CurrentTool.mouseReleaseEvent(self, event) | |
- def knifeMouseRelease(self, event): | |
- self.removeItem(self._knifeLine) | |
- self._knifeLine = None | |
- for dot in self._knifeDots: | |
- self.removeItem(dot) | |
- self._knifeDots = [] | |
- # reverse so as to not invalidate our cached segment indexes | |
- # XXX: multiple cuts on one segment don't work reliably | |
- self._cachedIntersections.reverse() | |
- if len(self._cachedIntersections): | |
- for intersect in self._cachedIntersections: | |
- contour, index, t = intersect | |
- contour.splitAndInsertPointAtSegmentAndT(index, t) | |
- self._cachedIntersections = [] | |
- event.accept() | |
+ def mouseDoubleClickEvent(self, event): | |
+ view = self.views()[0] | |
+ CurrentTool = view._currentTool | |
+ CurrentTool.mouseDoubleClickEvent(self, event) | |
class GlyphView(QGraphicsView): | |
def __init__(self, glyph, parent=None): | |
@@ -1302,7 +413,18 @@ class GlyphView(QGraphicsView): | |
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) | |
#self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) | |
- self.setSceneSelection() | |
+ tools = ( | |
+ selectionTool.SelectionTool, | |
+ penTool.PenTool, | |
+ rulerTool.RulerTool, | |
+ knifeTool.KnifeTool, | |
+ ) | |
+ for tool in tools: | |
+ self.registerTool(tool) | |
+ # TODO: rethink this | |
+ selectionButton = self.parent().toolBar.actions()[0] | |
+ selectionButton.setChecked(True) | |
+ self._currentTool = selectionButton.data() | |
self.setRenderHint(QPainter.Antialiasing) | |
self.scale(1, -1) | |
@@ -1340,6 +462,26 @@ class GlyphView(QGraphicsView): | |
# this will not be the case anymore when drag sidebearings pops up | |
scene._widthItem.setRect(0, -1000, self._glyph.width, 3000) | |
+ def registerTool(self, tool, activate=False): | |
+ # TODO: enclose this better | |
+ mainWindow = self.parent() | |
+ toolBar = mainWindow.toolBar | |
+ toolsGroup = mainWindow.toolsGroup | |
+ | |
+ instance = tool() | |
+ button = toolBar.addAction(instance.name(), self.setTool) | |
+ button.setCheckable(True) | |
+ button.setChecked(activate) | |
+ button.setData(instance) | |
+ button.setIcon(instance.icon()) | |
+ toolsGroup.addAction(button) | |
+ | |
+ def setTool(self, tool=None): | |
+ if tool is None: | |
+ tool = self.sender().data() | |
+ self._currentTool = tool | |
+ self.setDragMode(tool.dragMode()) | |
+ | |
def addBackground(self): | |
scene = self.scene() | |
font = self._glyph.getParent() | |
@@ -1568,22 +710,6 @@ class GlyphView(QGraphicsView): | |
self.setDragMode(QGraphicsView.RubberBandDrag) | |
super(GlyphView, self).mousePressEvent(event) | |
- def setSceneDrawing(self): | |
- self._currentTool = SceneTools.DrawingTool | |
- self.setDragMode(QGraphicsView.NoDrag) | |
- | |
- def setSceneRuler(self): | |
- self._currentTool = SceneTools.RulerTool | |
- self.setDragMode(QGraphicsView.NoDrag) | |
- | |
- def setSceneSelection(self): | |
- self._currentTool = SceneTools.SelectionTool | |
- self.setDragMode(QGraphicsView.RubberBandDrag) | |
- | |
- def setSceneKnife(self): | |
- self._currentTool = SceneTools.KnifeTool | |
- self.setDragMode(QGraphicsView.NoDrag) | |
- | |
# Lock/release handdrag does not seem to work… | |
''' | |
def mouseReleaseEvent(self, event): | |
diff --git a/Lib/defconQt/objects/qtItems.py b/Lib/defconQt/objects/qtItems.py | |
new file mode 100644 | |
index 0000000..d12c2b4 | |
--- /dev/null | |
+++ b/Lib/defconQt/objects/qtItems.py | |
@@ -0,0 +1,505 @@ | |
+from PyQt5.QtCore import QLineF, Qt | |
+from PyQt5.QtGui import QBrush, QColor, QFont, QPainterPath, QPen, QTransform | |
+from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsLineItem, QGraphicsEllipseItem, | |
+ QGraphicsPathItem, QGraphicsSimpleTextItem, QGraphicsRectItem, QGraphicsPixmapItem, | |
+ QStyle, QStyleOptionGraphicsItem) | |
+ | |
+def roundPosition(value): | |
+ value = value * 10#self._scale | |
+ value = round(value) - .5 | |
+ value = value * .1#self._inverseScale | |
+ return value | |
+ | |
+offCurvePointSize = 8#5 | |
+onCurvePointSize = 9#6 | |
+onCurveSmoothPointSize = 10#7 | |
+offWidth = offHeight = roundPosition(offCurvePointSize)# * self._inverseScale) | |
+offHalf = offWidth / 2.0 | |
+onWidth = onHeight = roundPosition(onCurvePointSize)# * self._inverseScale) | |
+onHalf = onWidth / 2.0 | |
+smoothWidth = smoothHeight = roundPosition(onCurveSmoothPointSize)# * self._inverseScale) | |
+smoothHalf = smoothWidth / 2.0 | |
+onCurvePenWidth = 1.5 | |
+offCurvePenWidth = 1.0 | |
+ | |
+anchorSize = 11 | |
+anchorWidth = anchorHeight = roundPosition(anchorSize) | |
+anchorHalf = anchorWidth / 2.0 | |
+ | |
+bezierHandleColor = QColor.fromRgbF(0, 0, 0, .2) | |
+startPointColor = QColor.fromRgbF(0, 0, 0, .2) | |
+backgroundColor = Qt.white | |
+offCurvePointColor = QColor.fromRgbF(1, 1, 1, 1) | |
+offCurvePointStrokeColor = QColor.fromRgbF(.6, .6, .6, 1) | |
+onCurvePointColor = offCurvePointStrokeColor | |
+onCurvePointStrokeColor = offCurvePointColor | |
+anchorColor = QColor(120, 120, 255) | |
+anchorSelectionColor = Qt.blue | |
+bluesColor = QColor.fromRgbF(.5, .7, 1, .3) | |
+fillColor = QColor(200, 200, 200, 120)#QColor.fromRgbF(0, 0, 0, .4) | |
+componentFillColor = QColor.fromRgbF(0, 0, 0, .4)#QColor.fromRgbF(.2, .2, .3, .4) | |
+metricsColor = QColor(70, 70, 70) | |
+pointSelectionColor = Qt.red | |
+ | |
+class HandleLineItem(QGraphicsLineItem): | |
+ def __init__(self, x1, y1, x2, y2, parent): | |
+ super(HandleLineItem, self).__init__(x1, y1, x2, y2, parent) | |
+ self.setPen(QPen(bezierHandleColor, 1.0)) | |
+ self.setFlag(QGraphicsItem.ItemStacksBehindParent) | |
+ | |
+class OffCurvePointItem(QGraphicsEllipseItem): | |
+ def __init__(self, x, y, parent=None): | |
+ super(OffCurvePointItem, self).__init__(parent) | |
+ # since we have a parent, setPos must be relative to it | |
+ self.setPointPath() | |
+ self.setPos(x, y) # TODO: abstract and use pointX-self.parent().pos().x() | |
+ self.setFlag(QGraphicsItem.ItemIsMovable) | |
+ self.setFlag(QGraphicsItem.ItemIsSelectable) | |
+ self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
+ self.setFlag(QGraphicsItem.ItemStacksBehindParent) | |
+ | |
+ self.setBrush(QBrush(offCurvePointColor)) | |
+ self._needsUngrab = False | |
+ | |
+ def itemChange(self, change, value): | |
+ if change == QGraphicsItem.ItemPositionChange: | |
+ if self.scene()._integerPlane: | |
+ value.setX(round(value.x())) | |
+ value.setY(round(value.y())) | |
+ if QApplication.keyboardModifiers() & Qt.ShiftModifier \ | |
+ and len(self.scene().selectedItems()) == 1: | |
+ ax = abs(value.x()) | |
+ ay = abs(value.y()) | |
+ if ay >= ax * 2: | |
+ value.setX(0) | |
+ elif ay > ax / 2: | |
+ avg = (ax + ay) / 2 | |
+ value.setX(copysign(avg, value.x())) | |
+ value.setY(copysign(avg, value.y())) | |
+ else: | |
+ value.setY(0) | |
+ elif change == QGraphicsItem.ItemPositionHasChanged: | |
+ self.parentItem()._CPMoved(value) | |
+ # TODO: consider what to do w offCurves | |
+ #elif change == QGraphicsItem.ItemSelectedHasChanged: | |
+ # pass#self.parentItem()._CPSelChanged(value) | |
+ return value | |
+ | |
+ #def mousePressEvent(self, event): | |
+ # if not self._needsUngrab and self.x() == 0 and self.y() == 0: | |
+ # event.ignore() | |
+ # super(OffCurvePointItem, self).mousePressEvent(event) | |
+ | |
+ def mouseReleaseEvent(self, event): | |
+ super(OffCurvePointItem, self).mouseReleaseEvent(event) | |
+ if self._needsUngrab: | |
+ self.ungrabMouse() | |
+ self._needsUngrab = False | |
+ | |
+ # http://www.qtfr.org/viewtopic.php?pid=21045#p21045 | |
+ def paint(self, painter, option, widget): | |
+ #if self.x() == 0 and self.y() == 0: return | |
+ newOption = QStyleOptionGraphicsItem(option) | |
+ newOption.state = QStyle.State_None | |
+ pen = self.pen() | |
+ if option.state & QStyle.State_Selected: | |
+ pen.setColor(pointSelectionColor) | |
+ else: | |
+ pen.setColor(offCurvePointStrokeColor) | |
+ self.setPen(pen) | |
+ super(OffCurvePointItem, self).paint(painter, newOption, widget) | |
+ | |
+ def setPointPath(self, scale=None): | |
+ if scale is None: | |
+ scene = self.scene() | |
+ if scene is not None: | |
+ scale = scene.getViewScale() | |
+ else: | |
+ scale = 1 | |
+ if scale > 4: scale = 4 | |
+ elif scale < .4: scale = .4 | |
+ self.prepareGeometryChange() | |
+ self.setRect(-offHalf/scale, -offHalf/scale, offWidth/scale, offHeight/scale) | |
+ self.setPen(QPen(offCurvePointStrokeColor, offCurvePenWidth/scale)) | |
+ | |
+class OnCurvePointItem(QGraphicsPathItem): | |
+ def __init__(self, x, y, isSmooth, contour, point, scale=1, parent=None): | |
+ super(OnCurvePointItem, self).__init__(parent) | |
+ self._contour = contour | |
+ self._point = point | |
+ self._isSmooth = isSmooth | |
+ self._posBeforeMove = None | |
+ | |
+ self.setPointPath(scale) | |
+ self.setPos(x, y) | |
+ self.setFlag(QGraphicsItem.ItemIsMovable) | |
+ self.setFlag(QGraphicsItem.ItemIsSelectable) | |
+ self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
+ self.setBrush(QBrush(onCurvePointColor)) | |
+ | |
+ def delete(self, preserveShape=True): | |
+ def findNextOnCurve(self, index=0): | |
+ for _ in self._contour: | |
+ if self._contour[index].segmentType is not None: | |
+ break | |
+ index = (index+1) % len(self._contour) | |
+ return index | |
+ | |
+ scene = self.scene() | |
+ glyph = scene._glyphObject | |
+ if len(self._contour.segments) < 2: | |
+ glyph.removeContour(self._contour) | |
+ else: | |
+ ptIndex = self.getPointIndex() | |
+ if self._contour.open and ptIndex == 0: | |
+ nextOnCurveIndex = findNextOnCurve(self, 1) | |
+ self._contour._points = self._contour[nextOnCurveIndex:] | |
+ self._contour[0].segmentType = "move" | |
+ self._contour.dirty = True | |
+ else: | |
+ # Using preserveShape at the edge of an open contour will traceback | |
+ if ptIndex == len(self._contour): preserveShape = False | |
+ self._contour.removeSegment(self.getSegmentIndex(), preserveShape) | |
+ nextOnCurveIndex = findNextOnCurve(self) | |
+ self._contour.setStartPoint(nextOnCurveIndex) | |
+ # This object will be removed from scene by notification mechanism | |
+ | |
+ def setPointPath(self, scale=None): | |
+ path = QPainterPath() | |
+ if scale is None: | |
+ scene = self.scene() | |
+ if scene is not None: | |
+ scale = scene.getViewScale() | |
+ else: | |
+ scale = 1 | |
+ if scale > 4: scale = 4 | |
+ elif scale < .4: scale = .4 | |
+ if self._isSmooth: | |
+ path.addEllipse(-smoothHalf/scale, -smoothHalf/scale, smoothWidth/scale, smoothHeight/scale) | |
+ else: | |
+ path.addRect(-onHalf/scale, -onHalf/scale, onWidth/scale, onHeight/scale) | |
+ self.prepareGeometryChange() | |
+ self.setPath(path) | |
+ self.setPen(QPen(onCurvePointStrokeColor, onCurvePenWidth/scale)) | |
+ | |
+ def getPointIndex(self): | |
+ return self._contour.index(self._point) | |
+ | |
+ def getSegmentIndex(self): | |
+ # closed contour cycles and so the "previous" segment goes to current point | |
+ index = 0 if self._contour.open else -1 | |
+ for pt in self._contour: | |
+ if pt == self._point: break | |
+ if pt.segmentType is not None: index += 1 | |
+ return index % len(self._contour.segments) | |
+ | |
+ def _CPMoved(self, newValue): | |
+ pointIndex = self.getPointIndex() | |
+ children = self.childItems() | |
+ # nodes are stored after lines (for stacking order) | |
+ if children[1].isSelected(): | |
+ selected = 1 | |
+ propagate = 3 | |
+ else: | |
+ selected = 3 | |
+ propagate = 1 | |
+ curValue = children[selected].pos() | |
+ line = children[selected-1].line() | |
+ children[selected-1].setLine(line.x1(), line.y1(), newValue.x(), newValue.y()) | |
+ | |
+ if not len(children) > 4: | |
+ # we need the modulo if the start point was to be an offCurve | |
+ elemIndex = (pointIndex-2+selected) % len(self._contour) | |
+ self._contour[elemIndex].x = self.pos().x()+newValue.x() | |
+ self._contour[elemIndex].y = self.pos().y()+newValue.y() | |
+ if not (self._isSmooth and children[propagate].isVisible()): | |
+ self.setShallowDirty() | |
+ return | |
+ if children[selected]._needsUngrab: | |
+ targetLen = children[selected-1].line().length()*2 | |
+ else: | |
+ targetLen = children[selected-1].line().length()+children[propagate-1].line().length() | |
+ tmpLine = QLineF(newValue, QPointF(0, 0)) | |
+ tmpLine.setLength(targetLen) | |
+ children[propagate].setFlag(QGraphicsItem.ItemSendsGeometryChanges, False) | |
+ children[propagate].setPos(tmpLine.x2(), tmpLine.y2()) | |
+ children[propagate].setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
+ children[propagate-1].setLine(line.x1(), line.y1(), tmpLine.x2(), tmpLine.y2()) | |
+ propagateInContour = (pointIndex-2+propagate) % len(self._contour) | |
+ self._contour[propagateInContour].x = self.pos().x()+tmpLine.x2() | |
+ self._contour[propagateInContour].y = self.pos().y()+tmpLine.y2() | |
+ self.setShallowDirty() | |
+ | |
+ def itemChange(self, change, value): | |
+ if change == QGraphicsItem.ItemPositionChange: | |
+ if self.scene()._integerPlane: | |
+ value.setX(round(value.x())) | |
+ value.setY(round(value.y())) | |
+ elif change == QGraphicsItem.ItemPositionHasChanged: | |
+ # have a look at defcon FuzzyNumber as well | |
+ pointIndex = self.getPointIndex() | |
+ self._contour[pointIndex].x = self.pos().x() | |
+ self._contour[pointIndex].y = self.pos().y() | |
+ | |
+ children = self.childItems() | |
+ if children[1].isVisible(): | |
+ prevPos = children[1].pos() | |
+ self._contour[pointIndex-1].x = self.pos().x()+prevPos.x() | |
+ self._contour[pointIndex-1].y = self.pos().y()+prevPos.y() | |
+ if children[3].isVisible(): | |
+ nextPos = children[3].pos() | |
+ self._contour[pointIndex+1].x = self.pos().x()+nextPos.x() | |
+ self._contour[pointIndex+1].y = self.pos().y()+nextPos.y() | |
+ self.setShallowDirty() | |
+ elif change == QGraphicsItem.ItemSelectedHasChanged: | |
+ self._point.selected = value | |
+ return value | |
+ | |
+ def setShallowDirty(self): | |
+ scene = self.scene() | |
+ scene._blocked = True | |
+ self._contour.dirty = True | |
+ scene._blocked = False | |
+ | |
+ def mouseMoveEvent(self, event): | |
+ modifiers = event.modifiers() | |
+ children = self.childItems() | |
+ # Ctrl: get and move prevCP, Alt: nextCP | |
+ if modifiers & Qt.ControlModifier and children[1].x() == 0 and children[1].y() == 0: | |
+ i, o = 1, 3 | |
+ elif modifiers & Qt.AltModifier and children[3].x() == 0 and children[3].y() == 0: | |
+ i, o = 3, 1 | |
+ elif not (modifiers & Qt.ControlModifier or modifiers & Qt.AltModifier): | |
+ super(OnCurvePointItem, self).mouseMoveEvent(event) | |
+ return | |
+ else: # eat the event if we are not going to yield an offCP | |
+ event.accept() | |
+ return | |
+ ptIndex = self.getPointIndex() | |
+ scene = self.scene() | |
+ scene._blocked = True | |
+ # if we have line segment, insert offCurve points | |
+ insertIndex = (ptIndex+(i-1)//2) % len(self._contour) | |
+ if self._contour[insertIndex].segmentType == "line": | |
+ nextToCP = self._contour[(ptIndex-2+i) % len(self._contour)] | |
+ assert(nextToCP.segmentType is not None) | |
+ self._contour[insertIndex].segmentType = "curve" | |
+ if i == 1: | |
+ first, second = (self._point.x, self._point.y), (nextToCP.x, nextToCP.y) | |
+ else: | |
+ first, second = (nextToCP.x, nextToCP.y), (self._point.x, self._point.y) | |
+ self._contour.insertPoint(insertIndex, self._contour._pointClass(first)) | |
+ self._contour.insertPoint(insertIndex, self._contour._pointClass(second)) | |
+ children[i].setVisible(True) | |
+ # TODO: need a list of items to make this efficient | |
+ scene.getItemForPoint(nextToCP).childItems()[o].setVisible(True) | |
+ # release current onCurve | |
+ scene.sendEvent(self, QEvent(QEvent.MouseButtonRelease)) | |
+ scene.mouseGrabberItem().ungrabMouse() | |
+ self.setSelected(False) | |
+ self.setIsSmooth(False) | |
+ children[i]._needsUngrab = True | |
+ scene.sendEvent(children[i], QEvent(QEvent.MouseButtonPress)) | |
+ children[i].setSelected(True) | |
+ children[i].grabMouse() | |
+ scene._blocked = False | |
+ event.accept() | |
+ | |
+ def setIsSmooth(self, isSmooth): | |
+ self._isSmooth = isSmooth | |
+ self._point.smooth = self._isSmooth | |
+ self.setShallowDirty() | |
+ self.setPointPath() | |
+ | |
+ # http://www.qtfr.org/viewtopic.php?pid=21045#p21045 | |
+ def paint(self, painter, option, widget): | |
+ newOption = QStyleOptionGraphicsItem(option) | |
+ newOption.state = QStyle.State_None | |
+ pen = self.pen() | |
+ if option.state & QStyle.State_Selected: | |
+ pen.setColor(pointSelectionColor) | |
+ else: | |
+ pen.setColor(onCurvePointStrokeColor) | |
+ self.setPen(pen) | |
+ super(OnCurvePointItem, self).paint(painter, newOption, widget) | |
+ | |
+class AnchorItem(QGraphicsPathItem): | |
+ def __init__(self, anchor, scale=1, parent=None): | |
+ super(AnchorItem, self).__init__(parent) | |
+ self._anchor = anchor | |
+ | |
+ textItem = QGraphicsSimpleTextItem(self._anchor.name, parent=self) | |
+ font = QFont() | |
+ font.setPointSize(9) | |
+ textItem.setFont(font) | |
+ textItem.setFlag(QGraphicsItem.ItemIgnoresTransformations) | |
+ self.setPointPath(scale) | |
+ self.setPos(self._anchor.x, self._anchor.y) | |
+ self.setFlag(QGraphicsItem.ItemIsMovable) | |
+ self.setFlag(QGraphicsItem.ItemIsSelectable) | |
+ self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
+ self.setBrush(QBrush(anchorColor)) | |
+ self.setPen(QPen(Qt.NoPen)) | |
+ | |
+ def delete(self): | |
+ glyph = self._anchor.getParent() | |
+ glyph.removeAnchor(self._anchor) | |
+ | |
+ def itemChange(self, change, value): | |
+ if change == QGraphicsItem.ItemPositionChange: | |
+ if self.scene()._integerPlane: | |
+ value.setX(round(value.x())) | |
+ value.setY(round(value.y())) | |
+ elif change == QGraphicsItem.ItemPositionHasChanged: | |
+ x = self.pos().x() | |
+ y = self.pos().y() | |
+ scene = self.scene() | |
+ scene._blocked = True | |
+ self._anchor.x = x | |
+ self._anchor.y = y | |
+ scene._blocked = False | |
+ return value | |
+ | |
+ def setPointPath(self, scale=None): | |
+ path = QPainterPath() | |
+ if scale is None: | |
+ scene = self.scene() | |
+ if scene is not None: | |
+ scale = scene.getViewScale() | |
+ else: | |
+ scale = 1 | |
+ if scale > 4: scale = 4 | |
+ elif scale < .4: scale = .4 | |
+ | |
+ path.moveTo(-anchorHalf/scale, 0) | |
+ path.lineTo(0, anchorHalf/scale) | |
+ path.lineTo(anchorHalf/scale, 0) | |
+ path.lineTo(0, -anchorHalf/scale) | |
+ path.closeSubpath() | |
+ | |
+ self.prepareGeometryChange() | |
+ self.setPath(path) | |
+ textItem = self.childItems()[0] | |
+ textItem.setPos(anchorHalf/scale, textItem.boundingRect().height()/2) | |
+ | |
+ # http://www.qtfr.org/viewtopic.php?pid=21045#p21045 | |
+ def paint(self, painter, option, widget): | |
+ newOption = QStyleOptionGraphicsItem(option) | |
+ newOption.state = QStyle.State_None | |
+ pen = self.pen() | |
+ if option.state & QStyle.State_Selected: | |
+ self.setBrush(anchorSelectionColor) | |
+ else: | |
+ self.setBrush(anchorColor) | |
+ super(AnchorItem, self).paint(painter, newOption, widget) | |
+ | |
+class ComponentItem(QGraphicsPathItem): | |
+ def __init__(self, path, component, parent=None): | |
+ super(ComponentItem, self).__init__(path, parent) | |
+ self._component = component | |
+ self.setTransform(QTransform(*component.transformation)) | |
+ self.setBrush(QBrush(componentFillColor)) | |
+ self.setFlag(QGraphicsItem.ItemIsMovable) | |
+ self.setFlag(QGraphicsItem.ItemIsSelectable) | |
+ self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) | |
+ | |
+ def delete(self): | |
+ glyph = self._component.getParent() | |
+ glyph.removeComponent(self._component) | |
+ | |
+ def itemChange(self, change, value): | |
+ if change == QGraphicsItem.ItemPositionChange: | |
+ if self.scene()._integerPlane: | |
+ value.setX(round(value.x())) | |
+ value.setY(round(value.y())) | |
+ elif change == QGraphicsItem.ItemPositionHasChanged: | |
+ t = self._component.transformation | |
+ x = self.pos().x() | |
+ y = self.pos().y() | |
+ scene = self.scene() | |
+ scene._blocked = True | |
+ self._component.transformation = (t[0], t[1], t[2], t[3], x, y) | |
+ scene._blocked = False | |
+ return value | |
+ | |
+class VGuidelinesTextItem(QGraphicsSimpleTextItem): | |
+ def __init__(self, text, font, parent=None): | |
+ super(VGuidelinesTextItem, self).__init__(text, parent) | |
+ self.setBrush(metricsColor) | |
+ self.setFlag(QGraphicsItem.ItemIgnoresTransformations) | |
+ self.setFont(font) | |
+ | |
+class ResizeHandleItem(QGraphicsRectItem): | |
+ def __init__(self, parent=None): | |
+ super(QGraphicsRectItem, self).__init__(parent) | |
+ self.setPointPath() | |
+ self.setBrush(QBrush(QColor(60, 60, 60))) | |
+ self.setPen(QPen(Qt.NoPen)) | |
+ self.setFlag(QGraphicsItem.ItemIgnoresParentOpacity) | |
+ self.setFlag(QGraphicsItem.ItemIsMovable) | |
+ #self.setFlag(QGraphicsItem.ItemIsSelectable) | |
+ self.setCursor(Qt.SizeFDiagCursor) | |
+ | |
+ rect = self.parentItem().boundingRect() | |
+ self.setPos(rect.width(), rect.height()) | |
+ | |
+ def itemChange(self, change, value): | |
+ if change == QGraphicsItem.ItemSelectedChange: | |
+ if not value: self.setVisible(value) | |
+ return value | |
+ | |
+ def mouseMoveEvent(self, event): | |
+ self.parentItem()._pixmapGeometryChanged(event) | |
+ | |
+ def setPointPath(self, scale=None): | |
+ if scale is None: | |
+ scene = self.scene() | |
+ if scene is not None: | |
+ scale = scene.getViewScale() | |
+ else: | |
+ scale = 1 | |
+ if scale > 4: scale = 4 | |
+ self.prepareGeometryChange() | |
+ self.setRect(-onHalf/scale, -onHalf/scale, onWidth/scale, onHeight/scale) | |
+ | |
+class PixmapItem(QGraphicsPixmapItem): | |
+ def __init__(self, x, y, pixmap, parent=None): | |
+ super(QGraphicsPixmapItem, self).__init__(pixmap, parent) | |
+ self.setPos(x, y) | |
+ self.setFlag(QGraphicsItem.ItemIsMovable) | |
+ self.setFlag(QGraphicsItem.ItemIsSelectable) | |
+ self.setTransform(QTransform().fromScale(1, -1)) | |
+ self.setOpacity(.5) | |
+ self.setZValue(-1) | |
+ | |
+ rect = self.boundingRect() | |
+ self._rWidth = rect.width() | |
+ self._rHeight = rect.height() | |
+ handle = ResizeHandleItem(self) | |
+ handle.setVisible(False) | |
+ | |
+ def _pixmapGeometryChanged(self, event): | |
+ modifiers = event.modifiers() | |
+ pos = event.scenePos() | |
+ if modifiers & Qt.ControlModifier: | |
+ # rotate | |
+ refLine = QLineF(self.x(), self.y(), self.x()+self._rWidth, self.y()-self._rHeight) | |
+ curLine = QLineF(self.x(), self.y(), pos.x(), pos.y()) | |
+ self.setRotation(refLine.angleTo(curLine)) | |
+ else: | |
+ # scale | |
+ dy = (pos.y() - self.y()) / self._rHeight | |
+ if modifiers & Qt.ShiftModifier: | |
+ # keep original aspect ratio | |
+ dx = -dy | |
+ else: | |
+ dx = (pos.x() - self.x()) / self._rWidth | |
+ self.setTransform(QTransform().fromScale(dx, dy)) | |
+ event.accept() | |
+ | |
+ def itemChange(self, change, value): | |
+ if change == QGraphicsItem.ItemSelectedChange: | |
+ children = self.childItems() | |
+ if not children[0].isUnderMouse(): | |
+ children[0].setVisible(value) | |
+ return value | |
diff --git a/Lib/defconQt/tools/__init__.py b/Lib/defconQt/tools/__init__.py | |
new file mode 100644 | |
index 0000000..e69de29 | |
diff --git a/Lib/defconQt/tools/baseTool.py b/Lib/defconQt/tools/baseTool.py | |
new file mode 100644 | |
index 0000000..e19c328 | |
--- /dev/null | |
+++ b/Lib/defconQt/tools/baseTool.py | |
@@ -0,0 +1,33 @@ | |
+from defconQt.glyphView import OnCurvePointItem | |
+from PyQt5.QtGui import QIcon | |
+from PyQt5.QtWidgets import QGraphicsView | |
+ | |
+class BaseTool(object): | |
+ def name(self): | |
+ return "Tool" | |
+ | |
+ def icon(self): | |
+ return QIcon() | |
+ | |
+ def dragMode(self): | |
+ return QGraphicsView.NoDrag | |
+ | |
+ def doesUndo(self): | |
+ return True | |
+ | |
+ # events | |
+ | |
+ def mousePressEvent(self, scene, event): | |
+ raise NotImplementedError | |
+ | |
+ def mouseMoveEvent(self, scene, event): | |
+ raise NotImplementedError | |
+ | |
+ def mouseReleaseEvent(self, scene, event): | |
+ raise NotImplementedError | |
+ | |
+ def mouseDoubleClickEvent(self, scene, event): | |
+ view = scene.views()[0] | |
+ touched = scene.itemAt(event.scenePos(), view.transform()) | |
+ if touched and isinstance(touched, OnCurvePointItem): | |
+ touched.setIsSmooth(not touched._isSmooth) | |
diff --git a/Lib/defconQt/tools/knifeTool.py b/Lib/defconQt/tools/knifeTool.py | |
new file mode 100644 | |
index 0000000..02d1b6b | |
--- /dev/null | |
+++ b/Lib/defconQt/tools/knifeTool.py | |
@@ -0,0 +1,69 @@ | |
+from defconQt.tools.baseTool import BaseTool | |
+from defconQt.util.bezierMath import computeIntersections, lineIntersection | |
+from PyQt5.QtCore import QPointF | |
+from PyQt5.QtGui import QIcon | |
+from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView | |
+ | |
+class KnifeTool(BaseTool): | |
+ def name(self): | |
+ return "Knife" | |
+ | |
+ def icon(self): | |
+ return QIcon("defconQt/resources/cut.svg") | |
+ | |
+ # events | |
+ | |
+ def mousePressEvent(self, scene, event): | |
+ scenePos = event.scenePos() | |
+ x, y = scenePos.x(), scenePos.y() | |
+ scene._knifeLine = scene.addLine(x, y, x, y) | |
+ event.accept() | |
+ | |
+ def mouseMoveEvent(self, scene, event): | |
+ # XXX: shouldnt have to do this, it seems mouseTracking is wrongly activated | |
+ if scene._knifeLine is None: return | |
+ for dot in scene._knifeDots: | |
+ scene.removeItem(dot) | |
+ scene._knifeDots = [] | |
+ scenePos = event.scenePos() | |
+ x, y = scenePos.x(), scenePos.y() | |
+ line = scene._knifeLine.line() | |
+ line.setP2(QPointF(x, y)) | |
+ # XXX: not nice | |
+ glyph = scene.views()[0]._glyph | |
+ scene._cachedIntersections = [] | |
+ for contour in glyph: | |
+ segments = contour.segments | |
+ for index, seg in enumerate(segments): | |
+ prev = segments[index-1][-1] | |
+ if len(seg) == 3: | |
+ i = computeIntersections(prev, seg[0], seg[1], seg[2], line.x1(), line.y1(), x, y) | |
+ else: | |
+ i = lineIntersection(prev.x, prev.y, seg[0].x, seg[0].y, line.x1(), line.y1(), x, y) | |
+ for pt in i: | |
+ scale = scene.getViewScale() | |
+ item = scene.addEllipse(-offHalf/scale, -offHalf/scale, offWidth/scale, offHeight/scale) | |
+ item.setPos(pt[0], pt[1]) | |
+ scene._cachedIntersections.append((contour, index, pt[2])) | |
+ scene._knifeDots.append(item) | |
+ scene._knifeLine.setLine(line) | |
+ event.accept() | |
+ | |
+ def mouseReleaseEvent(self, scene, event): | |
+ scene.removeItem(scene._knifeLine) | |
+ scene._knifeLine = None | |
+ for dot in scene._knifeDots: | |
+ scene.removeItem(dot) | |
+ scene._knifeDots = [] | |
+ # reverse so as to not invalidate our cached segment indexes | |
+ # XXX: multiple cuts on one segment don't work reliably | |
+ scene._cachedIntersections.reverse() | |
+ if len(scene._cachedIntersections): | |
+ for intersect in scene._cachedIntersections: | |
+ contour, index, t = intersect | |
+ contour.splitAndInsertPointAtSegmentAndT(index, t) | |
+ scene._cachedIntersections = [] | |
+ event.accept() | |
+ | |
+ def mouseDoubleClickEvent(self, scene, event): | |
+ pass | |
diff --git a/Lib/defconQt/tools/penTool.py b/Lib/defconQt/tools/penTool.py | |
new file mode 100644 | |
index 0000000..1634965 | |
--- /dev/null | |
+++ b/Lib/defconQt/tools/penTool.py | |
@@ -0,0 +1,184 @@ | |
+from defconQt.objects.defcon import TContour | |
+from defconQt.tools.baseTool import BaseTool | |
+from defconQt.glyphView import OffCurvePointItem, OnCurvePointItem | |
+from PyQt5.QtGui import QIcon | |
+from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView | |
+ | |
+# XXX: self._editing should go in there | |
+ | |
+class PenTool(BaseTool): | |
+ def name(self): | |
+ return "Pen" | |
+ | |
+ def icon(self): | |
+ return QIcon("defconQt/resources/curve.svg") | |
+ | |
+ # events | |
+ | |
+ def mousePressEvent(self, scene, event): | |
+ view = scene.views()[0] | |
+ touched = scene.itemAt(event.scenePos(), view.transform()) | |
+ scene._blocked = True | |
+ forceSelect = False | |
+ sel = scene.selectedItems() | |
+ x, y = event.scenePos().x(), event.scenePos().y() | |
+ if scene._integerPlane: | |
+ x, y = round(x), round(y) | |
+ # XXX: not sure why isinstance does not work here | |
+ if len(sel) == 1: | |
+ isLastOnCurve = type(sel[0]) is OnCurvePointItem and sel[0]._contour.open and \ | |
+ sel[0].getPointIndex() == len(sel[0]._contour)-1 | |
+ # TODO: reimplement convenience methods in OffCurvePointItem | |
+ isLastOffCurve = type(sel[0]) is OffCurvePointItem and sel[0].parentItem()._contour.open and \ | |
+ sel[0].parentItem().getPointIndex()+1 == len(sel[0].parentItem()._contour)-1 | |
+ if len(sel) == 1 and (isLastOffCurve or isLastOnCurve): | |
+ if isLastOnCurve: | |
+ lastContour = sel[0]._contour | |
+ else: | |
+ lastContour = sel[0].parentItem()._contour | |
+ if (touched and isinstance(touched, OnCurvePointItem)) and touched.getPointIndex() == 0 \ | |
+ and lastContour == touched._contour and len(lastContour) > 1: | |
+ # Changing the first point from move to line/curve will cycle and so close the contour | |
+ if isLastOffCurve: | |
+ lastContour.addPoint((x,y)) | |
+ lastContour[0].segmentType = "curve" | |
+ touched.childItems()[1].setVisible(True) | |
+ else: | |
+ lastContour[0].segmentType = "line" | |
+ elif touched and isinstance(touched, OnCurvePointItem): | |
+ QGraphicsScene.mousePressEvent(scene, event) | |
+ return | |
+ else: | |
+ if QApplication.keyboardModifiers() & Qt.ShiftModifier: | |
+ forceSelect = True | |
+ if isLastOnCurve: | |
+ refx = sel[0].x() | |
+ refy = sel[0].y() | |
+ else: | |
+ refx = sel[0].parentItem().x() | |
+ refy = sel[0].parentItem().y() | |
+ if abs(x-refx) > abs(y-refy): y = copysign(refy, y) | |
+ else: x = copysign(refx, x) | |
+ if isLastOffCurve: | |
+ lastContour.addPoint((x,y)) | |
+ lastContour.addPoint((x,y), "curve") | |
+ else: | |
+ lastContour.addPoint((x,y), "line") | |
+ item = OnCurvePointItem(x, y, False, lastContour, lastContour[-1], scene.getViewScale()) | |
+ scene.addItem(item) | |
+ for _ in range(2): | |
+ lineObj = HandleLineItem(0, 0, 0, 0, item) | |
+ CPObject = OffCurvePointItem(0, 0, item) | |
+ CPObject.setVisible(False) | |
+ if isLastOffCurve: | |
+ item.childItems()[1].setVisible(True) | |
+ lastContour.dirty = True | |
+ scene._editing = True | |
+ elif not (touched and isinstance(touched, OnCurvePointItem)): | |
+ nextC = TContour() | |
+ scene._glyphObject.appendContour(nextC) | |
+ nextC.addPoint((x,y), "move") | |
+ | |
+ item = OnCurvePointItem(x, y, False, scene._glyphObject[-1], scene._glyphObject[-1][-1], scene.getViewScale()) | |
+ scene.addItem(item) | |
+ for _ in range(2): | |
+ lineObj = HandleLineItem(0, 0, 0, 0, item) | |
+ CPObject = OffCurvePointItem(0, 0, item) | |
+ CPObject.setVisible(False) | |
+ scene._editing = True | |
+ scene._blocked = False | |
+ QGraphicsScene.mousePressEvent(scene, event) | |
+ # Since shift clamps, we might be missing the point in mousePressEvent | |
+ if forceSelect: item.setSelected(True) | |
+ | |
+ def mouseMoveEvent(self, scene, event): | |
+ if scene._editing is True: | |
+ sel = scene.selectedItems() | |
+ if len(sel) == 1: | |
+ if isinstance(sel[0], OnCurvePointItem) and (event.scenePos() - sel[0].pos()).manhattanLength() >= 2: | |
+ mouseGrabberItem = scene.mouseGrabberItem() | |
+ # If we drawn an onCurve w Shift and we're not touching the item, we wont have | |
+ # a mouse grabber (anyways), return early here. | |
+ if mouseGrabberItem is None: | |
+ event.accept() | |
+ return | |
+ scene._blocked = True | |
+ if len(sel[0]._contour) < 2: | |
+ # release current onCurve | |
+ scene.sendEvent(sel[0], QEvent(QEvent.MouseButtonRelease)) | |
+ mouseGrabberItem.ungrabMouse() | |
+ sel[0].setSelected(False) | |
+ # append an offCurve point and start moving it | |
+ sel[0]._contour.addPoint((event.scenePos().x(), event.scenePos().y())) | |
+ nextCP = sel[0].childItems()[3] | |
+ nextCP.setVisible(True) | |
+ nextCP._needsUngrab = True | |
+ #nextCP.setSelected(True) | |
+ scene.sendEvent(nextCP, QEvent(QEvent.MouseButtonPress)) | |
+ nextCP.grabMouse() | |
+ else: | |
+ # release current onCurve, delete from contour | |
+ scene.sendEvent(sel[0], QEvent(QEvent.MouseButtonRelease)) | |
+ mouseGrabberItem.ungrabMouse() | |
+ sel[0].setSelected(False) | |
+ | |
+ # construct a curve segment to the current point if there is not one | |
+ onCurve = sel[0]._point | |
+ if not onCurve.segmentType == "curve": | |
+ # remove the last onCurve | |
+ sel[0]._contour.removePoint(onCurve) | |
+ prev = sel[0]._contour[-1] | |
+ scene.getItemForPoint(prev).childItems()[3].setVisible(True) | |
+ # add a zero-length offCurve to the previous point | |
+ sel[0]._contour.addPoint((prev.x, prev.y)) | |
+ # add prevOffCurve and activate | |
+ sel[0]._contour.addPoint((sel[0].x(), sel[0].y())) | |
+ sel[0].childItems()[1].setVisible(True) | |
+ # add back current onCurve as a curve point | |
+ sel[0]._contour.addPoint((onCurve.x, onCurve.y), "curve") | |
+ sel[0]._point = sel[0]._contour[-1] | |
+ if not QApplication.keyboardModifiers() & Qt.AltModifier: | |
+ sel[0]._point.smooth = True | |
+ sel[0]._isSmooth = True | |
+ sel[0].setPointPath() | |
+ if sel[0].getPointIndex() == 0: | |
+ # we're probably dealing with the first point that we looped. | |
+ # preserve nextCP whatsoever. | |
+ lineObj = HandleLineItem(0, 0, 0, 0, sel[0]) | |
+ nextCP = OffCurvePointItem(0, 0, sel[0]) | |
+ # now we have l1, p1, l2, p2, l3, p3 | |
+ l2 = sel[0].childItems()[2] | |
+ lineObj.stackBefore(l2) | |
+ nextCP.stackBefore(l2) | |
+ else: | |
+ # add last offCurve | |
+ sel[0]._contour.addPoint((sel[0].x(), sel[0].y())) | |
+ nextCP = sel[0].childItems()[3] | |
+ nextCP._needsUngrab = True | |
+ nextCP.setVisible(True) | |
+ #nextCP.setSelected(True) | |
+ scene.sendEvent(nextCP, QEvent(QEvent.MouseButtonPress)) | |
+ nextCP.grabMouse() | |
+ scene._blocked = False | |
+ scene._editing = None | |
+ QGraphicsScene.mouseMoveEvent(scene, event) | |
+ else: | |
+ # eat the event | |
+ event.accept() | |
+ | |
+ def mouseReleaseEvent(self, scene, event): | |
+ # cleanup extra point elements if we dealt w curved first point | |
+ touched = scene.itemAt(event.scenePos(), scene.views()[0].transform()) | |
+ if touched and isinstance(touched, OffCurvePointItem): | |
+ onCurve = touched.parentItem() | |
+ children = onCurve.childItems() | |
+ if len(children) > 4: | |
+ # l1, p1, l3, p3, l2, p2 | |
+ children[3].prepareGeometryChange() | |
+ scene.removeItem(children[3]) | |
+ children[2].prepareGeometryChange() | |
+ scene.removeItem(children[2]) | |
+ | |
+ onCurve._isSmooth = False | |
+ onCurve.setPointPath() | |
+ onCurve._point.smooth = False | |
diff --git a/Lib/defconQt/tools/rulerTool.py b/Lib/defconQt/tools/rulerTool.py | |
new file mode 100644 | |
index 0000000..d024128 | |
--- /dev/null | |
+++ b/Lib/defconQt/tools/rulerTool.py | |
@@ -0,0 +1,96 @@ | |
+from defconQt.tools.baseTool import BaseTool | |
+from defconQt.glyphView import OffCurvePointItem, OnCurvePointItem | |
+from PyQt5.QtGui import QIcon, QPainterPath | |
+from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSimpleTextItem, QGraphicsView | |
+ | |
+class RulerTool(BaseTool): | |
+ def name(self): | |
+ return "Ruler" | |
+ | |
+ def icon(self): | |
+ return QIcon("defconQt/resources/ruler.svg") | |
+ | |
+ def doesUndo(self): | |
+ return False | |
+ | |
+ # events | |
+ | |
+ def mousePressEvent(self, scene, event): | |
+ touched = scene.itemAt(event.scenePos(), scene.views()[0].transform()) | |
+ if touched is not None and isinstance(touched, OnCurvePointItem) or \ | |
+ isinstance(touched, OffCurvePointItem): | |
+ x, y = touched.scenePos().x(), touched.scenePos().y() | |
+ else: | |
+ x, y = event.scenePos().x(), event.scenePos().y() | |
+ if scene._integerPlane: | |
+ x, y = round(x), round(y) | |
+ if scene._cachedRuler is not None: | |
+ scene.removeItem(scene._cachedRuler) | |
+ scene._cachedRuler = None | |
+ path = QPainterPath() | |
+ path.moveTo(x, y) | |
+ path.lineTo(x+1, y) | |
+ path.lineTo(x+1, y+1) | |
+ path.closeSubpath() | |
+ scene._rulerObject = scene.addPath(path) | |
+ textItem = QGraphicsSimpleTextItem("0", scene._rulerObject) | |
+ font = scene.font() | |
+ font.setPointSize(9) | |
+ textItem.setFont(font) | |
+ textItem.setFlag(QGraphicsItem.ItemIgnoresTransformations) | |
+ textItem.setPos(x, y + textItem.boundingRect().height()) | |
+ event.accept() | |
+ | |
+ def mouseMoveEvent(self, scene, event): | |
+ # XXX: shouldnt have to do this, it seems mouseTracking is wrongly activated | |
+ if scene._rulerObject is None: return | |
+ touched = scene.itemAt(event.scenePos(), scene.views()[0].transform()) | |
+ if touched is not None and isinstance(touched, OnCurvePointItem) or \ | |
+ isinstance(touched, OffCurvePointItem): | |
+ x, y = touched.scenePos().x(), touched.scenePos().y() | |
+ else: | |
+ # TODO: 45deg clamp w ShiftModifier | |
+ # maybe make a function for that + other occurences... | |
+ x, y = event.scenePos().x(), event.scenePos().y() | |
+ if scene._integerPlane: | |
+ x, y = round(x), round(y) | |
+ path = scene._rulerObject.path() | |
+ baseElem = path.elementAt(0) | |
+ path.setElementPositionAt(1, x, baseElem.y) | |
+ path.setElementPositionAt(2, x, y) | |
+ path.setElementPositionAt(3, baseElem.x, baseElem.y) | |
+ scene._rulerObject.setPath(path) | |
+ textItem = scene._rulerObject.childItems()[0] | |
+ line = QLineF(baseElem.x, baseElem.y, x, y) | |
+ l = line.length() | |
+ # XXX: angle() doesnt go by trigonometric direction. Weird. | |
+ # TODO: maybe split in positive/negative 180s (ff) | |
+ a = 360 - line.angle() | |
+ line.setP2(QPointF(x, baseElem.y)) | |
+ h = line.length() | |
+ line.setP1(QPointF(x, y)) | |
+ v = line.length() | |
+ text = "%d\n↔ %d\n↕ %d\nα %dº" % (l, h, v, a) | |
+ textItem.setText(text) | |
+ dx = x - baseElem.x | |
+ if dx >= 0: px = x | |
+ else: px = x - textItem.boundingRect().width() | |
+ dy = y - baseElem.y | |
+ if dy > 0: py = baseElem.y | |
+ else: py = baseElem.y + textItem.boundingRect().height() | |
+ textItem.setPos(px, py) | |
+ event.accept() | |
+ | |
+ def mouseReleaseEvent(self, scene, event): | |
+ textItem = scene._rulerObject.childItems()[0] | |
+ if textItem.text() == "0": | |
+ # delete no-op ruler | |
+ scene.removeItem(scene._rulerObject) | |
+ scene._rulerObject = None | |
+ else: | |
+ scene._cachedRuler = scene._rulerObject | |
+ scene._rulerObject = None | |
+ event.accept() | |
+ | |
+ def mouseDoubleClickEvent(self, scene, event): | |
+ pass | |
diff --git a/Lib/defconQt/tools/selectionTool.py b/Lib/defconQt/tools/selectionTool.py | |
new file mode 100644 | |
index 0000000..8896f71 | |
--- /dev/null | |
+++ b/Lib/defconQt/tools/selectionTool.py | |
@@ -0,0 +1,56 @@ | |
+from defconQt.tools.baseTool import BaseTool | |
+from defconQt.glyphView import OffCurvePointItem, OnCurvePointItem | |
+from PyQt5.QtGui import QIcon | |
+from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView | |
+ | |
+class SelectionTool(BaseTool): | |
+ def name(self): | |
+ return "Selection" | |
+ | |
+ def icon(self): | |
+ return QIcon("defconQt/resources/cursor.svg") | |
+ | |
+ def dragMode(self): | |
+ return QGraphicsView.RubberBandDrag | |
+ | |
+ # events | |
+ | |
+ def mousePressEvent(self, scene, event): | |
+ QGraphicsScene.mousePressEvent(scene, event) | |
+ | |
+ def mouseMoveEvent(self, scene, event): | |
+ items = scene.items(event.scenePos()) | |
+ # XXX: we must cater w mouse tracking | |
+ # we dont need isSelected() once its rid | |
+ mouseGrabberItem = scene.mouseGrabberItem() | |
+ """ | |
+ if len(items) > 1 and isinstance(items[0], OnCurvePointItem) and \ | |
+ isinstance(items[1], OffCurvePointItem) and items[1].isSelected(): | |
+ items[1].setPos(0, 0) | |
+ """ | |
+ if isinstance(mouseGrabberItem, OffCurvePointItem): | |
+ for item in items: | |
+ if isinstance(item, OnCurvePointItem): | |
+ print("CLAMP!") | |
+ mouseGrabberItem.setPos(0, 0) | |
+ event.accept() | |
+ return | |
+ QGraphicsScene.mouseMoveEvent(scene, event) | |
+ | |
+ def mouseReleaseEvent(self, scene, event): | |
+ scene._editing = False | |
+ # cleanup extra point elements if we dealt w curved first point | |
+ touched = scene.itemAt(event.scenePos(), scene.views()[0].transform()) | |
+ if touched and isinstance(touched, OffCurvePointItem): | |
+ onCurve = touched.parentItem() | |
+ children = onCurve.childItems() | |
+ if len(children) > 4: | |
+ # l1, p1, l3, p3, l2, p2 | |
+ children[3].prepareGeometryChange() | |
+ scene.removeItem(children[3]) | |
+ children[2].prepareGeometryChange() | |
+ scene.removeItem(children[2]) | |
+ | |
+ onCurve._isSmooth = False | |
+ onCurve.setPointPath() | |
+ onCurve._point.smooth = False | |
diff --git a/Lib/defconQt/util/bezierMath.py b/Lib/defconQt/util/bezierMath.py | |
new file mode 100644 | |
index 0000000..c1dc15c | |
--- /dev/null | |
+++ b/Lib/defconQt/util/bezierMath.py | |
@@ -0,0 +1,48 @@ | |
+from fontTools.misc import bezierTools | |
+ | |
+""" | |
+Computes intersection between a cubic spline and a line segment. | |
+Adapted from: https://www.particleincell.com/2013/cubic-line-intersection/ | |
+ | |
+Takes four defcon points describing curve and four scalars describing line | |
+parameters. | |
+""" | |
+def computeIntersections(scene, p1, p2, p3, p4, x1, y1, x2, y2): | |
+ bx, by = x1 - x2, y2 - y1 | |
+ m = x1*(y1-y2) + y1*(x2-x1) | |
+ a, b, c, d = bezierTools.calcCubicParameters((p1.x, p1.y), (p2.x, p2.y), | |
+ (p3.x, p3.y), (p4.x, p4.y)) | |
+ | |
+ pc0 = by*a[0] + bx*a[1] | |
+ pc1 = by*b[0] + bx*b[1] | |
+ pc2 = by*c[0] + bx*c[1] | |
+ pc3 = by*d[0] + bx*d[1] + m | |
+ r = bezierTools.solveCubic(pc0, pc1, pc2, pc3) | |
+ | |
+ sol = [] | |
+ for t in r: | |
+ s0 = a[0]*t**3 + b[0]*t**2 + c[0]*t + d[0] | |
+ s1 = a[1]*t**3 + b[1]*t**2 + c[1]*t + d[1] | |
+ if (x2-x1) != 0: | |
+ s = (s0-x1) / (x2-x1) | |
+ else: | |
+ s = (s1-y1) / (y2-y1) | |
+ if not (t < 0 or t > 1 or s < 0 or s > 1): | |
+ sol.append((s0, s1, t)) | |
+ return sol | |
+ | |
+""" | |
+G. Bach, http://stackoverflow.com/a/1968345 | |
+""" | |
+def lineIntersection(scene, x1, y1, x2, y2, x3, y3, x4, y4): | |
+ Bx_Ax = x2 - x1 | |
+ By_Ay = y2 - y1 | |
+ Dx_Cx = x4 - x3 | |
+ Dy_Cy = y4 - y3 | |
+ determinant = (-Dx_Cx * By_Ay + Bx_Ax * Dy_Cy) | |
+ if abs(determinant) < 1e-20: return [] | |
+ s = (-By_Ay * (x1 - x3) + Bx_Ax * (y1 - y3)) / determinant | |
+ t = ( Dx_Cx * (y1 - y3) - Dy_Cy * (x1 - x3)) / determinant | |
+ if s >= 0 and s <= 1 and t >= 0 and t <= 1: | |
+ return [(x1 + (t * Bx_Ax), y1 + (t * By_Ay), t)] | |
+ return [] | |
diff --git a/setup.py b/setup.py | |
index c0f3249..5323133 100644 | |
--- a/setup.py | |
+++ b/setup.py | |
@@ -44,6 +44,7 @@ setup(name="defconQt", | |
"defconQt.objects", | |
"defconQt.pens", | |
"defconQt.representationFactories", | |
+ "defconQt.tools", | |
], | |
package_dir={"":"Lib"} | |
) | |
\ No newline at end of file | |
-- | |
2.1.1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment