From 738371ef37946ceae47fa3d997d1e3998bbc116b Mon Sep 17 00:00:00 2001 From: yorikvanhavre Date: Sat, 24 Dec 2011 13:28:44 -0200 Subject: [PATCH 1/3] + extended DraftSnap (Git allows cool commit messages) The DraftSnap system gained an ortho X extension snap location, the ortho snap got fixed, and a new getPoint() method, which you can feed with a callback function to be called when a point has been clicked. --- src/Mod/Draft/DraftSnap.py | 415 ++++++++----------------------------- 1 file changed, 90 insertions(+), 325 deletions(-) diff --git a/src/Mod/Draft/DraftSnap.py b/src/Mod/Draft/DraftSnap.py index cbcdb6842..73065e694 100644 --- a/src/Mod/Draft/DraftSnap.py +++ b/src/Mod/Draft/DraftSnap.py @@ -38,7 +38,17 @@ class Snapper: and arch module to manage object snapping. It is responsible for finding snap points and displaying snap markers. Usually You only need to invoke it's snap() function, all the rest is taken - care of.""" + care of. + + 3 functions are useful for the scriptwriter: snap(), constrain() + or getPoint() which is an all-in-one combo. + + The indivudual snapToXXX() functions return a snap definition in + the form [real_point,marker_type,visual_point], and are not + meant to be used directly, they are all called when necessary by + the general snap() function. + + """ def __init__(self): self.lastObj = [None,None] @@ -57,6 +67,7 @@ class Snapper: self.extLine = None self.grid = None self.constrainLine = None + self.trackLine = None # the snapmarker has "dot","circle" and "square" available styles self.mk = {'passive':'circle', @@ -98,6 +109,8 @@ class Snapper: self.unconstrain() return point + snaps = [] + # type conversion if needed if isinstance(screenpos,list): screenpos = tuple(screenpos) @@ -141,7 +154,7 @@ class Snapper: info = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo((screenpos[0],screenpos[1])) # checking if parallel to one of the edges of the last objects - point = self.snapToExtensions(point,lastpoint) + point = self.snapToExtensions(point,lastpoint,constrain) if not info: @@ -152,7 +165,6 @@ class Snapper: else: # we have an object to snap to - snaps = [] obj = FreeCAD.ActiveDocument.getObject(info['Object']) if not obj: @@ -241,8 +253,22 @@ class Snapper: # return the final point return cstr(winner[2]) - def snapToExtensions(self,point,last): + def snapToExtensions(self,point,last,constrain): "returns a point snapped to extension or parallel line to last object, if any" + + tsnap = self.snapToExtOrtho(last,constrain) + if tsnap: + if (tsnap[0].sub(point)).Length < self.radius: + if self.tracker: + self.tracker.setCoords(tsnap[2]) + self.tracker.setMarker(self.mk[tsnap[1]]) + self.tracker.on() + if self.extLine: + self.extLine.p2(tsnap[2]) + self.extLine.on() + self.setCursor(tsnap[1]) + return tsnap[2] + for o in [self.lastObj[1],self.lastObj[0]]: if o: ob = FreeCAD.ActiveDocument.getObject(o) @@ -333,24 +359,30 @@ class Snapper: def snapToOrtho(self,shape,last,constrain): "returns a list of ortho snap locations" snaps = [] - if constrain != None: + if constrain: if isinstance(shape,Part.Edge): if last: if isinstance(shape.Curve,Part.Line): - p1 = shape.Vertexes[0].Point - p2 = shape.Vertexes[-1].Point - if (constrain == 0): - if ((last.y > p1.y) and (last.y < p2.y) or (last.y > p2.y) and (last.y < p1.y)): - pc = (last.y-p1.y)/(p2.y-p1.y) - cp = (Vector(p1.x+pc*(p2.x-p1.x),p1.y+pc*(p2.y-p1.y),p1.z+pc*(p2.z-p1.z))) - snaps.append([cp,'ortho',cp]) - elif (constrain == 1): - if ((last.x > p1.x) and (last.x < p2.x) or (last.x > p2.x) and (last.x < p1.x)): - pc = (last.x-p1.x)/(p2.x-p1.x) - cp = (Vector(p1.x+pc*(p2.x-p1.x),p1.y+pc*(p2.y-p1.y),p1.z+pc*(p2.z-p1.z))) - snaps.append([cp,'ortho',cp]) + if self.constraintAxis: + tmpEdge = Part.Line(last,last.add(self.constraintAxis)).toShape() + # get the intersection points + pt = fcgeo.findIntersection(tmpEdge,shape,True,True) + if pt: + for p in pt: + snaps.append([p,'ortho',p]) return snaps + def snapToExtOrtho(self,last,constrain): + "returns an ortho X extension snap location" + if constrain and last and self.constraintAxis and self.extLine: + tmpEdge1 = Part.Line(last,last.add(self.constraintAxis)).toShape() + tmpEdge2 = Part.Line(self.extLine.p1(),self.extLine.p2()).toShape() + # get the intersection points + pt = fcgeo.findIntersection(tmpEdge1,tmpEdge2,True,True) + if pt: + return [pt[0],'ortho',pt[0]] + return None + def snapToAngles(self,shape): "returns a list of angle snap locations" snaps = [] @@ -511,316 +543,49 @@ class Snapper: self.affinity = None if self.constrainLine: self.constrainLine.off() - -# deprecated ################################################################## -# last snapped objects, for quick intersection calculation -lastObj = [0,0] + def getPoint(self,last=None,callback=None): + """getPoint([last],[callback]) : gets a 3D point from the screen. You can provide an existing point, + in that case additional snap options and a tracker are available. You can also passa function as + callback, which will get called with the resulting point as argument, when a point is clicked.""" + self.pt = None + self.ui = FreeCADGui.draftToolBar + self.view = FreeCADGui.ActiveDocument.ActiveView + + # setting a track line if we got an existing point + if last: + if not self.trackLine: + self.trackLine = DraftTrackers.lineTracker() + self.trackLine.p1(last) + self.trackLine.on() + + def move(event_cb): + event = event_cb.getEvent() + mousepos = event.getPosition() + ctrl = event.wasCtrlDown() + shift = event.wasShiftDown() + self.pt = FreeCADGui.Snapper.snap(mousepos,lastpoint=last,active=ctrl,constrain=shift) + self.ui.displayPoint(self.pt,last,plane=FreeCAD.DraftWorkingPlane,mask=FreeCADGui.Snapper.affinity) + if self.trackLine: + self.trackLine.p2(self.pt) -def snapPoint(target,point,cursor,ctrl=False): - ''' - Snap function used by the Draft tools - - Currently has two modes: passive and active. Pressing CTRL while - clicking puts you in active mode: - - - In passive mode (an open circle appears), your point is - snapped to the nearest point on any underlying geometry. - - - In active mode (ctrl pressed, a filled circle appears), your point - can currently be snapped to the following points: - - Nodes and midpoints of all Part shapes - - Nodes and midpoints of lines/wires - - Centers and quadrant points of circles - - Endpoints of arcs - - Intersection between line, wires segments, arcs and circles - - When constrained (SHIFT pressed), Intersections between - constraining axis and lines/wires - ''' - - def getConstrainedPoint(edge,last,constrain): - "check for constrained snappoint" - p1 = edge.Vertexes[0].Point - p2 = edge.Vertexes[-1].Point - ar = [] - if (constrain == 0): - if ((last.y > p1.y) and (last.y < p2.y) or (last.y > p2.y) and (last.y < p1.y)): - pc = (last.y-p1.y)/(p2.y-p1.y) - cp = (Vector(p1.x+pc*(p2.x-p1.x),p1.y+pc*(p2.y-p1.y),p1.z+pc*(p2.z-p1.z))) - ar.append([cp,1,cp]) # constrainpoint - if (constrain == 1): - if ((last.x > p1.x) and (last.x < p2.x) or (last.x > p2.x) and (last.x < p1.x)): - pc = (last.x-p1.x)/(p2.x-p1.x) - cp = (Vector(p1.x+pc*(p2.x-p1.x),p1.y+pc*(p2.y-p1.y),p1.z+pc*(p2.z-p1.z))) - ar.append([cp,1,cp]) # constrainpoint - return ar - - def getPassivePoint(info): - "returns a passive snap point" - cur = Vector(info['x'],info['y'],info['z']) - return [cur,2,cur] - - def getScreenDist(dist,cursor): - "returns a 3D distance from a screen pixels distance" - p1 = FreeCADGui.ActiveDocument.ActiveView.getPoint(cursor) - p2 = FreeCADGui.ActiveDocument.ActiveView.getPoint((cursor[0]+dist,cursor[1])) - return (p2.sub(p1)).Length - - def getGridSnap(target,point): - "returns a grid snap point if available" - if target.grid: - return target.grid.getClosestNode(point) - return None - - def getPerpendicular(edge,last): - "returns a point on an edge, perpendicular to the given point" - dv = last.sub(edge.Vertexes[0].Point) - nv = fcvec.project(dv,fcgeo.vec(edge)) - np = (edge.Vertexes[0].Point).add(nv) - return np - - # checking if alwaySnap setting is on - extractrl = False - if Draft.getParam("alwaysSnap"): - extractrl = ctrl - ctrl = True - - # setting Radius - radius = getScreenDist(Draft.getParam("snapRange"),cursor) - - # checking if parallel to one of the edges of the last objects - target.snap.off() - target.extsnap.off() - if (len(target.node) > 0): - for o in [lastObj[1],lastObj[0]]: - if o: - ob = target.doc.getObject(o) - if ob: - if ob.isDerivedFrom("Part::Feature"): - edges = ob.Shape.Edges - if len(edges)<10: - for e in edges: - if isinstance(e.Curve,Part.Line): - last = target.node[len(target.node)-1] - de = Part.Line(last,last.add(fcgeo.vec(e))).toShape() - np = getPerpendicular(e,point) - if (np.sub(point)).Length < radius: - target.snap.coords.point.setValue((np.x,np.y,np.z)) - target.snap.setMarker("circle") - target.snap.on() - target.extsnap.p1(e.Vertexes[0].Point) - target.extsnap.p2(np) - target.extsnap.on() - point = np - else: - last = target.node[len(target.node)-1] - de = Part.Line(last,last.add(fcgeo.vec(e))).toShape() - np = getPerpendicular(de,point) - if (np.sub(point)).Length < radius: - target.snap.coords.point.setValue((np.x,np.y,np.z)) - target.snap.setMarker("circle") - target.snap.on() - point = np - - # check if we snapped to something - snapped=target.view.getObjectInfo((cursor[0],cursor[1])) - - if (snapped == None): - # nothing has been snapped, check fro grid snap - gpt = getGridSnap(target,point) - if gpt: - if radius != 0: - dv = point.sub(gpt) - if dv.Length <= radius: - target.snap.coords.point.setValue((gpt.x,gpt.y,gpt.z)) - target.snap.setMarker("point") - target.snap.on() - return gpt - return point - else: - # we have something to snap - obj = target.doc.getObject(snapped['Object']) - if hasattr(obj.ViewObject,"Selectable"): - if not obj.ViewObject.Selectable: - return point - if not ctrl: - # are we in passive snap? - snapArray = [getPassivePoint(snapped)] - else: - snapArray = [] - comp = snapped['Component'] - if obj.isDerivedFrom("Part::Feature"): - if "Edge" in comp: - # get the stored objects to calculate intersections - intedges = [] - if lastObj[0]: - lo = target.doc.getObject(lastObj[0]) - if lo: - if lo.isDerivedFrom("Part::Feature"): - intedges = lo.Shape.Edges - - nr = int(comp[4:])-1 - edge = obj.Shape.Edges[nr] - for v in edge.Vertexes: - snapArray.append([v.Point,0,v.Point]) - if isinstance(edge.Curve,Part.Line): - # the edge is a line - midpoint = fcgeo.findMidpoint(edge) - snapArray.append([midpoint,1,midpoint]) - if (len(target.node) > 0): - last = target.node[len(target.node)-1] - snapArray.extend(getConstrainedPoint(edge,last,target.constrain)) - np = getPerpendicular(edge,last) - snapArray.append([np,1,np]) - - elif isinstance (edge.Curve,Part.Circle): - # the edge is an arc - rad = edge.Curve.Radius - pos = edge.Curve.Center - for i in [0,30,45,60,90,120,135,150,180,210,225,240,270,300,315,330]: - ang = math.radians(i) - cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z) - snapArray.append([cur,1,cur]) - for i in [15,37.5,52.5,75,105,127.5,142.5,165,195,217.5,232.5,255,285,307.5,322.5,345]: - ang = math.radians(i) - cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z) - snapArray.append([cur,0,pos]) - - for e in intedges: - # get the intersection points - pt = fcgeo.findIntersection(e,edge) - if pt: - for p in pt: - snapArray.append([p,3,p]) - elif "Vertex" in comp: - # directly snapped to a vertex - p = Vector(snapped['x'],snapped['y'],snapped['z']) - snapArray.append([p,0,p]) - elif comp == '': - # workaround for the new view provider - p = Vector(snapped['x'],snapped['y'],snapped['z']) - snapArray.append([p,2,p]) - else: - snapArray = [getPassivePoint(snapped)] - elif Draft.getType(obj) == "Dimension": - for pt in [obj.Start,obj.End,obj.Dimline]: - snapArray.append([pt,0,pt]) - elif Draft.getType(obj) == "Mesh": - for v in obj.Mesh.Points: - snapArray.append([v.Vector,0,v.Vector]) - if not lastObj[0]: - lastObj[0] = obj.Name - lastObj[1] = obj.Name - if (lastObj[1] != obj.Name): - lastObj[0] = lastObj[1] - lastObj[1] = obj.Name - - # calculating shortest distance - shortest = 1000000000000000000 - spt = Vector(snapped['x'],snapped['y'],snapped['z']) - newpoint = [Vector(0,0,0),0,Vector(0,0,0)] - for pt in snapArray: - if pt[0] == None: print "snapPoint: debug 'i[0]' is 'None'" - di = pt[0].sub(spt) - if di.Length < shortest: - shortest = di.Length - newpoint = pt - if radius != 0: - dv = point.sub(newpoint[2]) - if (not extractrl) and (dv.Length > radius): - newpoint = getPassivePoint(snapped) - target.snap.coords.point.setValue((newpoint[2].x,newpoint[2].y,newpoint[2].z)) - if (newpoint[1] == 1): - target.snap.setMarker("square") - elif (newpoint[1] == 0): - target.snap.setMarker("point") - elif (newpoint[1] == 3): - target.snap.setMarker("square") - else: - target.snap.setMarker("circle") - target.snap.on() - return newpoint[2] - -def constrainPoint (target,pt,mobile=False,sym=False): - ''' - Constrain function used by the Draft tools - On commands that need to enter several points (currently only line/wire), - you can constrain the next point to be picked to the last drawn point by - pressing SHIFT. The vertical or horizontal constraining depends on the - position of your mouse in relation to last point at the moment you press - SHIFT. if mobile=True, mobile behaviour applies. If sym=True, x alway = y - ''' - point = Vector(pt) - if len(target.node) > 0: - last = target.node[-1] - dvec = point.sub(last) - affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(dvec) - if ((target.constrain == None) or mobile): - if affinity == "x": - dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.u) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(FreeCAD.DraftWorkingPlane.u) > 1: - l = -l - point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l))) - target.constrain = 0 #x direction - target.ui.xValue.setEnabled(True) - target.ui.yValue.setEnabled(False) - target.ui.zValue.setEnabled(False) - target.ui.xValue.setFocus() - elif affinity == "y": - dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.v) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(FreeCAD.DraftWorkingPlane.v) > 1: - l = -l - point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l))) - target.constrain = 1 #y direction - target.ui.xValue.setEnabled(False) - target.ui.yValue.setEnabled(True) - target.ui.zValue.setEnabled(False) - target.ui.yValue.setFocus() - elif affinity == "z": - dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.axis) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(FreeCAD.DraftWorkingPlane.axis) > 1: - l = -l - point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l))) - target.constrain = 2 #z direction - target.ui.xValue.setEnabled(False) - target.ui.yValue.setEnabled(False) - target.ui.zValue.setEnabled(True) - target.ui.zValue.setFocus() - else: target.constrain = 3 - elif (target.constrain == 0): - dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.u) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(FreeCAD.DraftWorkingPlane.u) > 1: - l = -l - point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l))) - elif (target.constrain == 1): - dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.v) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(FreeCAD.DraftWorkingPlane.u) > 1: - l = -l - point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l))) - elif (target.constrain == 2): - dv = fcvec.project(dvec,FreeCAD.DraftWorkingPlane.axis) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(FreeCAD.DraftWorkingPlane.u) > 1: - l = -l - point = last.add(FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(l,l,l))) - return point + def click(event_cb): + event = event_cb.getEvent() + if event.getState() == coin.SoMouseButtonEvent.DOWN: + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),self.callbackClick) + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),self.callbackMove) + FreeCADGui.Snapper.off() + self.ui.offUi() + if self.trackLine: + self.trackLine.off() + if callback: + callback(self.pt) + self.pt = None + # adding 2 callback functions + self.ui.pointUi() + self.callbackClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),click) + self.callbackMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),move) + if not hasattr(FreeCADGui,"Snapper"): FreeCADGui.Snapper = Snapper() From 1fc47ecc1a0b0d7e7cffda14f97a6b123dee1596 Mon Sep 17 00:00:00 2001 From: yorikvanhavre Date: Sat, 24 Dec 2011 13:35:01 -0200 Subject: [PATCH 2/3] + extended DraftSnap --- src/Mod/Draft/DraftSnap.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/DraftSnap.py b/src/Mod/Draft/DraftSnap.py index 73065e694..78f955ce3 100644 --- a/src/Mod/Draft/DraftSnap.py +++ b/src/Mod/Draft/DraftSnap.py @@ -547,7 +547,12 @@ class Snapper: def getPoint(self,last=None,callback=None): """getPoint([last],[callback]) : gets a 3D point from the screen. You can provide an existing point, in that case additional snap options and a tracker are available. You can also passa function as - callback, which will get called with the resulting point as argument, when a point is clicked.""" + callback, which will get called with the resulting point as argument, when a point is clicked: + + def cb(point): + print "got a 3D point: ",point + FreeCADGui.Snapper.getPoint(callback=cb) + """ self.pt = None self.ui = FreeCADGui.draftToolBar self.view = FreeCADGui.ActiveDocument.ActiveView From 9cf345ecfb0b2c6a54f7e6dc4773a9461143d1e8 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sat, 24 Dec 2011 14:31:30 -0200 Subject: [PATCH 3/3] + small fix in DraftSnap --- src/Mod/Draft/DraftSnap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Mod/Draft/DraftSnap.py b/src/Mod/Draft/DraftSnap.py index 78f955ce3..f2f0b3d25 100644 --- a/src/Mod/Draft/DraftSnap.py +++ b/src/Mod/Draft/DraftSnap.py @@ -225,6 +225,9 @@ class Snapper: self.lastObj[0] = self.lastObj[1] self.lastObj[1] = obj.Name + if not snaps: + return point + # calculating the nearest snap point shortest = 1000000000000000000 origin = Vector(info['x'],info['y'],info['z'])