diff --git a/CadEventFilter.py b/CadEventFilter.py index a6d6cb7..2f6b25f 100644 --- a/CadEventFilter.py +++ b/CadEventFilter.py @@ -25,8 +25,12 @@ from qgis.core import * from qgis.gui import * +from CadPointList import CadPointList +from CadIntersection import * + import math + class CadEventFilter(QObject): """ This class manages the events for the MapCanvas. @@ -43,10 +47,8 @@ def __init__(self, iface, inputwidget): self.mapCanvas = iface.mapCanvas() self.inputwidget = inputwidget - #input coordinates - self.p1 = QgsPoint() # previous click, used for delta angles - self.p2 = QgsPoint() # last click, used for delta positions - self.p3 = QgsPoint() # current position + # store points + self.cadPointList = CadPointList(inputwidget) self.snapSegment = None # segment snapped at current position (if any) self.snapPoint = None # point snapped at current position (if any) @@ -110,13 +112,14 @@ def eventFilter(self, obj, event): # Set the current mouse position (either from snapPoint, from snapSegment, or regular coordinate transform) if self.snapPoint is not None: - p3 = QgsPoint(self.snapPoint) + curPoint = QgsPoint(self.snapPoint) elif self.snapSegment is not None: - p3 = self.snapSegment[0] + curPoint = self.snapSegment[0] else: - p3 = self.iface.mapCanvas().getCoordinateTransform().toMapCoordinates( event.pos() ) + curPoint = self.iface.mapCanvas().getCoordinateTransform().toMapCoordinates( event.pos() ) - self.p3 = self._constrain(p3) + curPoint = self._constrain(curPoint) + self.cadPointList.updateCurrentPoint(curPoint) # Depending on the mode... @@ -142,13 +145,13 @@ def eventFilter(self, obj, event): if event.type() == QEvent.MouseButtonPress or event.type() == QEvent.MouseButtonRelease: #B2a. Mouse press input mode self.createSnappingPoint() - modifiedEvent = QMouseEvent( event.type(), self._toPixels(self.p3), event.button(), event.buttons(), event.modifiers() ) + modifiedEvent = QMouseEvent( event.type(), self._toPixels(curPoint), event.button(), event.buttons(), event.modifiers() ) QCoreApplication.sendEvent(obj,modifiedEvent) self.removeSnappingPoint() else: #B2B. Mouse move input mode - modifiedEvent = QMouseEvent( event.type(), self._toPixels(self.p3), event.button(), event.buttons(), event.modifiers() ) + modifiedEvent = QMouseEvent( event.type(), self._toPixels(curPoint), event.button(), event.buttons(), event.modifiers() ) QCoreApplication.sendEvent(obj,modifiedEvent) # We unlock all the inputs, since we don't want locking to stay for the next point (actually, sometimes we do, this could be an option) @@ -157,17 +160,23 @@ def eventFilter(self, obj, event): if event.type() == QEvent.MouseButtonRelease: # In input mode (B), we register the last points for following relative calculation in case of mousePress - self.p1 = self.p2 - self.p2 = self.p3 - + self.cadPointList.newPoint() # By returning True, we inform the eventSystem that the event must not be sent further (since a new event has been sent through QCoreApplication) return True + elif self.inputwidget.active and event.type() == QEvent.KeyPress: # We redirect all key inputs to the inputwidget. self.inputwidget.keyPressEvent(event) # If the event is not accpted, we return False, so the event is propagated to the MapCanvas (and normal shortcuts should work) return event.isAccepted() + elif event.type() == QEvent.MouseButtonRelease and event.button() == Qt.RightButton: + # cancel digitization on right click + self.cadPointList.empty() + self.snapSegment = None # segment snapped at current position (if any) + self.snapPoint = None # point snapped at current position (if any) + QCoreApplication.sendEvent(obj,event) + return True else: #In case we don't manage this type of event, or if it was already treated (spontaneous==False), we return the normal implementation return QObject.eventFilter(self, obj, event) @@ -177,101 +186,103 @@ def eventFilter(self, obj, event): ##### CONSTRAINING ##### ######################## - def _constrain(self, p3): + def _constrain(self, point): """ This method returns a point constrained by the w's settings and, by the way, updates the w's displayed values. """ + previousPoint = self.cadPointList.previousPoint() + penulPoint = self.cadPointList.penultimatePoint() + + if previousPoint is not None: + dx = point.x()-previousPoint.x() + dy = point.y()-previousPoint.y() + dist = math.sqrt( point.sqrDist(previousPoint)) + canConstrainAbsoluteAngle = True + canConstrainDistance = True + canConstrainRelativePos = True + else: + dx, dy, dist = None, None, None + canConstrainAbsoluteAngle = False + canConstrainDistance = False + canConstrainRelativePos = False + + if penulPoint is not None: + ddx = previousPoint.x() - penulPoint.x() + ddy = previousPoint.y() - penulPoint.y() + canConstrainRelativeAngle = True + else: + ddx, ddy = None, None + canConstrainRelativeAngle = False - #X + ################# + # X constrain if self.inputwidget.lx: if self.inputwidget.rx: - p3.setX( self.p2.x() + self.inputwidget.x ) + if canConstrainRelativePos: + point.setX( previousPoint.x() + self.inputwidget.x ) + else: + # TODO raise error? + pass else: - p3.setX( self.inputwidget.x ) + point.setX( self.inputwidget.x ) if self.snapSegment is not None and not self.inputwidget.ly: # we will magnietize to the intersection of that segment and the lockedX ! - x = p3.x() - - x1 = self.snapSegment[1].x() - y1 = self.snapSegment[1].y() - x2 = self.snapSegment[2].x() - y2 = self.snapSegment[2].y() - - dx = x2 - x1 - dy = y2 - y1 - - if dy==0: - y = y1 - else: - y = y1+(dy * (x-x1) ) / dx - - p3.setY( y ) - + y = CadIntersection.LineIntersectionAtX( self.snapSegment[1], self.snapSegment[2], point.x() ) + point.setY( y ) else: if self.inputwidget.rx: - self.inputwidget.x = p3.x()-self.p2.x() + self.inputwidget.x = dx else: - self.inputwidget.x = p3.x() - - + self.inputwidget.x = point.x() - #Y + ################# + # Y constrain if self.inputwidget.ly: if self.inputwidget.ry: - p3.setY( self.p2.y() + self.inputwidget.y ) + if canConstrainRelativeAngle: + point.setY( previousPoint.y() + self.inputwidget.y ) + else: + # TODO raise error? + pass else: - p3.setY( self.inputwidget.y ) + point.setY( self.inputwidget.y ) if self.snapSegment is not None and not self.inputwidget.lx: - # we will magnietize to the intersection of that segment and the lockedY ! - - y = p3.y() - - x1 = self.snapSegment[1].x() - y1 = self.snapSegment[1].y() - x2 = self.snapSegment[2].x() - y2 = self.snapSegment[2].y() - - dx = x2 - x1 - dy = y2 - y1 - - if dy==0: - x = x1 - else: - x = x1+(dx * (y-y1) ) / dy - - p3.setX( x ) - + # we will magnietize to the intersection of that segment and the lockedY ! + x = CadIntersection.LineIntersectionAtY( self.snapSegment[1], self.snapSegment[2], point.y() ) + point.setX( x ) else: if self.inputwidget.ry: - self.inputwidget.y = p3.y()-self.p2.y() + self.inputwidget.y = dy else: - self.inputwidget.y = p3.y() + self.inputwidget.y = point.y() - #A - dx = p3.x()-self.p2.x() - dy = p3.y()-self.p2.y() - - if self.inputwidget.la: + ################# + # Angle constrain + if self.inputwidget.la and canConstrainAbsoluteAngle: a = self.inputwidget.a/180.0*math.pi if self.inputwidget.ra: - # We compute the angle relative to the last segment (0° is aligned with last segment) - lastA = math.atan2(self.p2.y() - self.p1.y(), self.p2.x() - self.p1.x()) - a = lastA+a + if canConstrainRelativeAngle: + # We compute the angle relative to the last segment (0° is aligned with last segment) + lastA = math.atan2(ddy, ddx) + a += lastA + else: + # TODO raise error? + pass cosA = math.cos( a ) sinA = math.sin( a ) v1 = [ cosA, sinA ] v2 = [ dx, dy ] vP = v1[0]*v2[0]+v1[1]*v2[1] - p3.set( self.p2.x()+cosA*vP, self.p2.y()+sinA*vP) + point.set( previousPoint.x()+cosA*vP, previousPoint.y()+sinA*vP) if self.snapSegment is not None and not self.inputwidget.ld: # we will magnietize to the intersection of that segment and the lockedAngle ! - l1 = QLineF(self.p2.x(), self.p2.y(), self.p2.x()+math.cos(a), self.p2.y()+math.sin(a)) + l1 = QLineF(previousPoint.x(), previousPoint.y(), previousPoint.x()+math.cos(a), previousPoint.y()+math.sin(a)) l2 = QLineF(self.snapSegment[1].x(), self.snapSegment[1].y(), self.snapSegment[2].x() ,self.snapSegment[2].y()) intP = QPointF() @@ -279,101 +290,73 @@ def _constrain(self, p3): t = 0.0001 # TODO : this may cause some accuracy problem ? if l1.intersect(l2, intP) == QLineF.UnboundedIntersection and not (ang < t or ang > 360-t or (ang > 180-t and ang < 180+t) ): - p3.setX( intP.x() ) - p3.setY( intP.y() ) - + point.setX( intP.x() ) + point.setY( intP.y() ) else: - if self.inputwidget.ra: - lastA = math.atan2(self.p2.y() - self.p1.y(), self.p2.x() - self.p1.x()) + if self.inputwidget.ra and canConstrainRelativeAngle: + lastA = math.atan2(ddy, ddx) self.inputwidget.a = (math.atan2( dy, dx )-lastA)/math.pi*180 - else: + elif not self.inputwidget.ra and canConstrainAbsoluteAngle: self.inputwidget.a = math.atan2( dy, dx )/math.pi*180 + else: + self.inputwidget.a = None - - - #D - dx = p3.x()-self.p2.x() - dy = p3.y()-self.p2.y() - dist = math.sqrt( dx*dx + dy*dy ) - - if self.inputwidget.ld: + ################# + # Distance constrain + if canConstrainDistance and self.inputwidget.ld: if dist == 0: # handle case where mouse is over origin and distance constraint is enabled # take arbitrary horizontal line - p3.set( self.p2.x()+self.inputwidget.d, self.p2.y() ) + point.set( previousPoint.x()+self.inputwidget.d, previousPoint.y() ) else: vP = self.inputwidget.d / dist - p3.set( self.p2.x()+dx*vP, self.p2.y()+dy*vP ) + point.set( previousPoint.x()+dx*vP, previousPoint.y()+dy*vP ) if self.snapSegment is not None and not self.inputwidget.la: # we will magnietize to the intersection of that segment and the lockedDistance ! - # formula taken from http://mathworld.wolfram.com/Circle-LineIntersection.html - - xo = self.p2.x() - yo = self.p2.y() - - x1 = self.snapSegment[1].x()-xo - y1 = self.snapSegment[1].y()-yo - x2 = self.snapSegment[2].x()-xo - y2 = self.snapSegment[2].y()-yo - - r = self.inputwidget.d - - dx = x2-x1 - dy = y2-y1 - dr = math.sqrt(dx**2+dy**2) - d = x1*y2-x2*y1 - - def sgn(x): return -1 if x<0 else 1 - - DISC = r**2 * dr**2 - d**2 - - if DISC<=0: - #no intersection or tangeant - pass + p1, p2 = CadIntersection.CircleLineIntersection(self.snapSegment[1], self.snapSegment[2], + previousPoint, self.inputwidget.d) + #we snap to the nearest intersection + if point.sqrDist(p1) < point.sqrDist(p2): + point.set( p1.x(), p1.y() ) else: - #first possible point - ax = xo + (d*dy+sgn(dy)*dx*math.sqrt(r**2*dr**2-d**2))/(dr**2) - ay = yo + (-d*dx+abs(dy)*math.sqrt(r**2*dr**2-d**2))/(dr**2) - - #second possible point - bx = xo + (d*dy-sgn(dy)*dx*math.sqrt(r**2*dr**2-d**2))/(dr**2) - by = yo + (-d*dx-abs(dy)*math.sqrt(r**2*dr**2-d**2))/(dr**2) - - #we snap to the nearest intersection - if (ax-p3.x())**2+(ay-p3.y())**2 >= (bx-p3.x())**2+(by-p3.y())**2: - p3.setX( bx ) - p3.setY( by ) - else: - p3.setX( ax ) - p3.setY( ay ) + point.set( p2.x(), p2.y() ) else: self.inputwidget.d = dist - #Update the widget's x&y values (for display only) if self.inputwidget.rx: - self.inputwidget.x = p3.x()-self.p2.x() - else: - self.inputwidget.x = p3.x() + if canConstrainRelativePos: + self.inputwidget.x = dx + else: + self.inputwidget.rx = False + if not self.inputwidget.rx: + self.inputwidget.x = point.x() if self.inputwidget.ry: - self.inputwidget.y = p3.y()-self.p2.y() - else: - self.inputwidget.y = p3.y() + if canConstrainRelativePos: + self.inputwidget.y = dy + else: + self.inputwidget.ry = False + if not self.inputwidget.ry: + self.inputwidget.y = point.y() + + return point - return p3 def _alignToSegment(self): """ Set's the CadWidget's angle value to be parrelel to self.snapSegment's angle """ + + previousPoint = self.cadPointList.previousPoint() + penulPoint = self.cadPointList.penultimatePoint() if self.snapSegment is not None: angle = math.atan2( self.snapSegment[1].y()-self.snapSegment[2].y(), self.snapSegment[1].x()-self.snapSegment[2].x() ) if self.inputwidget.ra: - lastangle = math.atan2(self.p2.y()-self.p1.y(),self.p2.x()-self.p1.x()) + lastangle = math.atan2(previousPoint.y()-penulPoint.y(),previousPoint.x()-penulPoint.x()) angle -= lastangle if self.inputwidget.par: @@ -447,7 +430,7 @@ def createSnappingPoint(self): QgsProject.instance().blockSignals(False) #we don't want to refresh the snapping UI feature = QgsFeature() - feature.setGeometry( QgsGeometry.fromPoint( self.p3 ) ) + feature.setGeometry( QgsGeometry.fromPoint( self.cadPointList.currentPoint() ) ) provider.addFeatures([feature]) self.memoryLayer.updateExtents() diff --git a/CadInputWidget.py b/CadInputWidget.py index 4ff0841..e372f14 100644 --- a/CadInputWidget.py +++ b/CadInputWidget.py @@ -104,6 +104,9 @@ def disableIfEnabled(state, target): self.widX.installEventFilter(self.linEditFilter) self.widY.installEventFilter(self.linEditFilter) + # not point at start, do not allow setting constraints + self.enableConstraints(0) + # And finally add to the MainWindow self.iface.mainWindow().addDockWidget(Qt.LeftDockWidgetArea, self) @@ -188,6 +191,35 @@ def maptoolChanged(self): self.active = (self.iface.mapCanvas().mapTool() is not None and self.iface.mapCanvas().mapTool().isEditTool()) + def enableConstraints(self, pointCount, previousCount=None): + if previousCount is not None and ( previousCount == pointCount or previousCount >= 3 and pointCount >= 3): + # do not trigger UI changes if nothing changed + return + + # relative coordinatesonly available with 1 previous point + self.relX.setEnabled(pointCount>=2) + self.relY.setEnabled(pointCount>=2) + # uncheck relative if there is not enough point + if pointCount < 2: + self.relX.setChecked(False) + self.relY.setChecked(False) + + # absolute angle only possible with 1 previous point + self.widA.setEnabled(pointCount>=2) + self.lockA.setEnabled(pointCount>=2) + # relative angle only available with 2 previous point + self.relA.setEnabled(pointCount>=3) + # uncheck relative if there is not enough point + if pointCount < 3: + self.relA.setChecked(False) + if previousCount == 2 and pointCount == 3: + # we we can have relative angle, set checked by default + self.relA.setChecked(True) + + # relative distance only available with 1 previous point + self.widD.setEnabled(pointCount>=2) + self.lockD.setEnabled(pointCount>=2) + """ Those properties are just to lighten the code in CadEventFilter @@ -216,38 +248,38 @@ def a(self, value): self.widA.setText(str(value)) #Lock properties @property - def lx(self): return self.lockX.isChecked() + def lx(self): return self.lockX.isEnabled() and self.lockX.isChecked() @lx.setter def lx(self, value): self.lockX.setChecked(value) @property - def ly(self): return self.lockY.isChecked() + def ly(self): return self.lockY.isEnabled() and self.lockY.isChecked() @ly.setter def ly(self, value): self.lockY.setChecked(value) @property - def la(self): return self.lockA.isChecked() + def la(self): return self.lockA.isEnabled() and self.lockA.isChecked() @la.setter def la(self, value): self.lockA.setChecked(value) @property - def ld(self): return self.lockD.isChecked() + def ld(self): return self.lockD.isEnabled() and self.lockD.isChecked() @ld.setter def ld(self, value): self.lockD.setChecked(value) #Relative properties @property - def rx(self): return self.relX.isChecked() + def rx(self): return self.relX.isEnabled() and self.relX.isChecked() @rx.setter def rx(self, value): self.relX.setChecked(value) @property - def ry(self): return self.relY.isChecked() + def ry(self): return self.relY.isEnabled() and self.relY.isChecked() @ry.setter def ry(self, value): self.relY.setChecked(value) @property - def ra(self): return self.relA.isChecked() + def ra(self): return self.relA.isEnabled() and self.relA.isChecked() @ra.setter def ra(self, value): self.relA.setChecked(value) @@ -256,6 +288,8 @@ def rd(self): return True @rd.setter def rd(self, value): raise Exception + + #Misc properties @property def enabled(self): return self.enableAction.isChecked() diff --git a/CadIntersection.py b/CadIntersection.py new file mode 100644 index 0000000..a8782eb --- /dev/null +++ b/CadIntersection.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + CadInput + A QGIS plugin + Provides CAD-like input globally : digitize features with precise numerical input for the angle, the distance, and easily make widCuctions lines + ------------------- + begin : 2014-02-05 + copyright : (C) 2014 by Olivier Dalang + email : olivier.dalang@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.core import QgsPoint +from math import sqrt + + +class CadIntersection(): + + @staticmethod + def LineIntersectionAtX(linePoint1, linePoint2, x): + x1 = linePoint1.x() + y1 = linePoint1.y() + x2 = linePoint2.x() + y2 = linePoint2.y() + dx = x2 - x1 + dy = y2 - y1 + if dy==0: + y = y1 + else: + y = y1+(dy * (x-x1) ) / dx + return y + + + @staticmethod + def LineIntersectionAtY(linePoint1, linePoint2, y): + x1 = linePoint1.x() + y1 = linePoint1.y() + x2 = linePoint2.x() + y2 = linePoint2.y() + dx = x2 - x1 + dy = y2 - y1 + if dy==0: + x = x1 + else: + x = x1+(dx * (y-y1) ) / dy + return x + + + @staticmethod + def CircleLineIntersection(linePoint1, linePoint2, circleCenter, r): + # formula taken from http://mathworld.wolfram.com/Circle-LineIntersection.html + + x1 = linePoint1.x()-circleCenter.x() + y1 = linePoint1.y()-circleCenter.y() + x2 = linePoint2.x()-circleCenter.x() + y2 = linePoint2.y()-circleCenter.y() + + dx = x2-x1 + dy = y2-y1 + dr = sqrt(dx**2+dy**2) + d = x1*y2-x2*y1 + + def sgn(x): return -1 if x<0 else 1 + + DISC = r**2 * dr**2 - d**2 + + if DISC<=0: + #no intersection or tangeant + pass + else: + #first possible point + ax = circleCenter.x() + (d*dy+sgn(dy)*dx*sqrt(r**2*dr**2-d**2))/(dr**2) + ay = circleCenter.y() + (-d*dx+abs(dy)*sqrt(r**2*dr**2-d**2))/(dr**2) + + #second possible point + bx = circleCenter.x() + (d*dy-sgn(dy)*dx*sqrt(r**2*dr**2-d**2))/(dr**2) + by = circleCenter.y() + (-d*dx-abs(dy)*sqrt(r**2*dr**2-d**2))/(dr**2) + + return QgsPoint(ax,ay), QgsPoint(bx,by) diff --git a/CadPaintWidget.py b/CadPaintWidget.py index 4d1b1e4..fbdbdb7 100644 --- a/CadPaintWidget.py +++ b/CadPaintWidget.py @@ -58,6 +58,9 @@ def paintEvent(self, paintEvent): """ Paints the visual feedback (painting is done in screen coordinates). """ + curPoint = self.eventfilter.cadPointList.currentPoint() + prevPoint = self.eventfilter.cadPointList.previousPoint() + penulPoint = self.eventfilter.cadPointList.penultimatePoint() if math.isnan( self._tX(0) ) or not self.inputwidget.active or not self.inputwidget.enabled: #on loading QGIS, it seems QgsMapToPixel is not ready and return NaNs... @@ -88,11 +91,12 @@ def paintEvent(self, paintEvent): 20, 20 ) - painter.setPen( pSnapLine ) - painter.drawLine( x, - y, - self._tX( self.eventfilter.p3.x()), - self._tY( self.eventfilter.p3.y()) ) + if curPoint is not None: + painter.setPen( pSnapLine ) + painter.drawLine( x, + y, + self._tX( curPoint.x()), + self._tY( curPoint.y()) ) #Draw segment snap @@ -105,11 +109,12 @@ def paintEvent(self, paintEvent): self._tY( self.eventfilter.snapSegment[2].y()) ) - painter.setPen( pSnapLine ) - painter.drawLine( self._tX( self.eventfilter.snapSegment[1].x()), - self._tY( self.eventfilter.snapSegment[1].y()), - self._tX( self.eventfilter.p3.x()), - self._tY( self.eventfilter.p3.y()) ) + if curPoint is not None: + painter.setPen( pSnapLine ) + painter.drawLine( self._tX( self.eventfilter.snapSegment[1].x()), + self._tY( self.eventfilter.snapSegment[1].y()), + self._tX( curPoint.x()), + self._tY( curPoint.y()) ) #Draw segment par/per input @@ -123,89 +128,91 @@ def paintEvent(self, paintEvent): #Draw angle - if self.inputwidget.ra: - a0 = math.atan2( -(self.eventfilter.p2.y()-self.eventfilter.p1.y()), self.eventfilter.p2.x()-self.eventfilter.p1.x() ) - a = a0-math.radians(self.inputwidget.a) - else: - a0 = 0 - a = -math.radians(self.inputwidget.a) - - painter.setPen( pConstruction2 ) - painter.drawArc( self._tX( self.eventfilter.p2.x())-20, - self._tY( self.eventfilter.p2.y())-20, - 40, 40, - 16*math.degrees(-a0), - 16*self.inputwidget.a ) - painter.drawLine( self._tX( self.eventfilter.p2.x()), - self._tY( self.eventfilter.p2.y()), - self._tX( self.eventfilter.p2.x())+60*math.cos(a0), - self._tY( self.eventfilter.p2.y())+60*math.sin(a0) ) - - if self.inputwidget.la: - painter.setPen( pLocked ) - painter.drawLine( self._tX( self.eventfilter.p2.x())-self.width()*math.cos(a), - self._tY( self.eventfilter.p2.y())-self.width()*math.sin(a), - self._tX( self.eventfilter.p2.x())+self.width()*math.cos(a), - self._tY( self.eventfilter.p2.y())+self.width()*math.sin(a) ) - + if penulPoint is not None: + if self.inputwidget.ra: + a0 = math.atan2( -(prevPoint.y()-penulPoint.y()), prevPoint.x()-penulPoint.x() ) + a = a0-math.radians(self.inputwidget.a) + else: + a0 = 0 + a = -math.radians(self.inputwidget.a) + painter.setPen( pConstruction2 ) + painter.drawArc( self._tX( prevPoint.x())-20, + self._tY( prevPoint.y())-20, + 40, 40, + 16*math.degrees(-a0), + 16*self.inputwidget.a ) + painter.drawLine( self._tX( prevPoint.x()), + self._tY( prevPoint.y()), + self._tX( prevPoint.x())+60*math.cos(a0), + self._tY( prevPoint.y())+60*math.sin(a0) ) + + if self.inputwidget.la: + painter.setPen( pLocked ) + painter.drawLine( self._tX( prevPoint.x())-self.width()*math.cos(a), + self._tY( prevPoint.y())-self.width()*math.sin(a), + self._tX( prevPoint.x())+self.width()*math.cos(a), + self._tY( prevPoint.y())+self.width()*math.sin(a) ) #Draw distance - if self.inputwidget.ld: - painter.setPen( pLocked ) - painter.drawEllipse( self._tX( self.eventfilter.p2.x() - self.inputwidget.d ), - self._tY( self.eventfilter.p2.y() + self.inputwidget.d ), - self._f( 2.0*self.inputwidget.d ), - self._f( 2.0*self.inputwidget.d ) ) + if prevPoint is not None: + if self.inputwidget.ld: + painter.setPen( pLocked ) + painter.drawEllipse( self._tX( prevPoint.x() - self.inputwidget.d ), + self._tY( prevPoint.y() + self.inputwidget.d ), + self._f( 2.0*self.inputwidget.d ), + self._f( 2.0*self.inputwidget.d ) ) #Draw x - if self.inputwidget.lx: - painter.setPen( pLocked ) - if self.inputwidget.rx: - x = self._tX( self.eventfilter.p2.x()+self.inputwidget.x ) - else: - x = self._tX( self.inputwidget.x ) - painter.drawLine( x, - 0, - x, - self.height() ) - - #Draw y - if self.inputwidget.ly: - painter.setPen( pLocked ) - if self.inputwidget.ry: - y = self._tY( self.eventfilter.p2.y()+self.inputwidget.y ) - else: - y = self._tY( self.inputwidget.y ) - painter.drawLine( 0, - y, - self.width(), - y ) + if prevPoint is not None: + if self.inputwidget.lx: + painter.setPen( pLocked ) + if self.inputwidget.rx: + x = self._tX( prevPoint.x()+self.inputwidget.x ) + else: + x = self._tX( self.inputwidget.x ) + painter.drawLine( x, + 0, + x, + self.height() ) + + #Draw y + if self.inputwidget.ly: + painter.setPen( pLocked ) + if self.inputwidget.ry: + y = self._tY( prevPoint.y()+self.inputwidget.y ) + else: + y = self._tY( self.inputwidget.y ) + painter.drawLine( 0, + y, + self.width(), + y ) #Draw constr - if not self.inputwidget.par and not self.inputwidget.per: - painter.setPen( pConstruction2 ) - painter.drawLine( self._tX( self.eventfilter.p2.x()), - self._tY( self.eventfilter.p2.y()), - self._tX( self.eventfilter.p3.x()), - self._tY( self.eventfilter.p3.y()) ) - - painter.setPen( pConstruction1 ) - painter.drawLine( self._tX( self.eventfilter.p1.x()), - self._tY( self.eventfilter.p1.y()), - self._tX( self.eventfilter.p2.x()), - self._tY( self.eventfilter.p2.y()) ) - - painter.setPen( pCursor ) - painter.drawLine( self._tX( self.eventfilter.p3.x())-5, - self._tY( self.eventfilter.p3.y())-5, - self._tX( self.eventfilter.p3.x())+5, - self._tY( self.eventfilter.p3.y())+5 ) - painter.drawLine( self._tX( self.eventfilter.p3.x())-5, - self._tY( self.eventfilter.p3.y())+5, - self._tX( self.eventfilter.p3.x())+5, - self._tY( self.eventfilter.p3.y())-5 ) + if penulPoint is not None and prevPoint is not None and curPoint is not None: + if not self.inputwidget.par and not self.inputwidget.per: + painter.setPen( pConstruction2 ) + painter.drawLine( self._tX( prevPoint.x()), + self._tY( prevPoint.y()), + self._tX( curPoint.x()), + self._tY( curPoint.y()) ) + + painter.setPen( pConstruction1 ) + painter.drawLine( self._tX( penulPoint.x()), + self._tY( penulPoint.y()), + self._tX( prevPoint.x()), + self._tY( prevPoint.y()) ) + + painter.setPen( pCursor ) + painter.drawLine( self._tX( curPoint.x())-5, + self._tY( curPoint.y())-5, + self._tX( curPoint.x())+5, + self._tY( curPoint.y())+5 ) + painter.drawLine( self._tX( curPoint.x())-5, + self._tY( curPoint.y())+5, + self._tX( curPoint.x())+5, + self._tY( curPoint.y())-5 ) diff --git a/CadPointList.py b/CadPointList.py new file mode 100644 index 0000000..addddb7 --- /dev/null +++ b/CadPointList.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + CadInput + A QGIS plugin + Provides CAD-like input globally : digitize features with precise numerical input for the angle, the distance, and easily make widCuctions lines + ------------------- + begin : 2014-02-04 + copyright : (C) 2014 by Denis Rouzaud + email : denis.rouzaud@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.core import QgsPoint +from math import sqrt + +class CadPointList(list): + """ + Sub-class of list to save list of QgsPoint + """ + def __init__(self, inputWidget): + list.__init__(self) + self.inputWidget = inputWidget + + def empty(self): + prevCount = len(self) + self[:] = [] + self.inputWidget.enableConstraints(0, prevCount) + self.inputWidget.unlockAll() + + def updateCurrentPoint(self, point): + if len(self)>0: + self[0] = point + else: + self.insert(0, point) + self.inputWidget.enableConstraints(1, 0) + + def newPoint(self): + prevCount = len(self) + self.insert(0, self[0]) + self.inputWidget.enableConstraints(prevCount+1, prevCount) + + def currentPoint(self): + if len(self): + return self[0] + else: + return None + + def previousPoint(self): + if len(self) > 1: + return self[1] + else: + return None + + def penultimatePoint(self): + if len(self) > 2: + return self[2] + else: + return None + diff --git a/ui_dock.py b/ui_dock.py index d13ce78..5769fbe 100644 --- a/ui_dock.py +++ b/ui_dock.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui_dock.ui' # -# Created: Tue Feb 4 13:45:33 2014 +# Created: Wed Feb 5 08:43:31 2014 # by: PyQt4 UI code generator 4.10.3 # # WARNING! All changes made in this file will be lost! @@ -69,16 +69,12 @@ def setupUi(self, CadInputDock): self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) self.gridLayout_2 = QtGui.QGridLayout() self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.relD = QtGui.QToolButton(self.dockWidgetContents) + self.relA = QtGui.QToolButton(self.dockWidgetContents) icon4 = QtGui.QIcon() icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/cadinput/resources/delta.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.relD.setIcon(icon4) - self.relD.setCheckable(True) - self.relD.setObjectName(_fromUtf8("relD")) - self.gridLayout_2.addWidget(self.relD, 0, 0, 1, 1) - self.relA = QtGui.QToolButton(self.dockWidgetContents) self.relA.setIcon(icon4) self.relA.setCheckable(True) + self.relA.setChecked(True) self.relA.setObjectName(_fromUtf8("relA")) self.gridLayout_2.addWidget(self.relA, 1, 0, 1, 1) self.relY = QtGui.QToolButton(self.dockWidgetContents) @@ -159,7 +155,6 @@ def retranslateUi(self, CadInputDock): self.widPer.setText(_translate("CadInputDock", "...", None)) self.widPar.setToolTip(_translate("CadInputDock", "Parallel", None)) self.widPar.setText(_translate("CadInputDock", "...", None)) - self.relD.setText(_translate("CadInputDock", "...", None)) self.relA.setText(_translate("CadInputDock", "...", None)) self.relY.setText(_translate("CadInputDock", "...", None)) self.relX.setText(_translate("CadInputDock", "...", None)) diff --git a/ui_dock.ui b/ui_dock.ui index e0218b6..73f0b2d 100644 --- a/ui_dock.ui +++ b/ui_dock.ui @@ -123,20 +123,6 @@ - - - - ... - - - - :/plugins/cadinput/resources/delta.png:/plugins/cadinput/resources/delta.png - - - true - - - @@ -149,6 +135,9 @@ true + + true +