diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 593095a2e..fc50e12fc 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -5,6 +5,8 @@ SET(Draft_SRCS Draft.py DraftTools.py DraftGui.py + DraftSnap.py + DraftTrackers.py WorkingPlane.py importDXF.py importOCA.py diff --git a/src/Mod/Draft/DraftSnap.py b/src/Mod/Draft/DraftSnap.py new file mode 100644 index 000000000..d34e0c1d9 --- /dev/null +++ b/src/Mod/Draft/DraftSnap.py @@ -0,0 +1,338 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2011 * +#* Yorik van Havre * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU General Public License (GPL) * +#* as published by the Free Software Foundation; either version 2 of * +#* the License, or (at your option) any later version. * +#* for detail see the LICENCE text file. * +#* * +#* This program is distributed in the hope that it will be useful, * +#* but WITHOUT ANY WARRANTY; without even the implied warranty of * +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +#* GNU Library General Public License for more details. * +#* * +#* You should have received a copy of the GNU Library General Public * +#* License along with this program; if not, write to the Free Software * +#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +#* USA * +#* * +#*************************************************************************** + +__title__="FreeCAD Draft Snap tools" +__author__ = "Yorik van Havre" +__url__ = "http://free-cad.sourceforge.net" + +import FreeCAD, math, Draft, +from draftlibs import fcvec,fcgeo +from FreeCAD import Vector +from pivy import coin + +# last snapped objects, for quick intersection calculation +lastObj = [0,0] + +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: + 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 = plane.getClosestAxis(dvec) + if ((target.constrain == None) or mobile): + if affinity == "x": + dv = fcvec.project(dvec,plane.u) + point = last.add(dv) + if sym: + l = dv.Length + if dv.getAngle(plane.u) > 1: + l = -l + point = last.add(plane.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,plane.v) + point = last.add(dv) + if sym: + l = dv.Length + if dv.getAngle(plane.v) > 1: + l = -l + point = last.add(plane.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,plane.axis) + point = last.add(dv) + if sym: + l = dv.Length + if dv.getAngle(plane.axis) > 1: + l = -l + point = last.add(plane.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,plane.u) + point = last.add(dv) + if sym: + l = dv.Length + if dv.getAngle(plane.u) > 1: + l = -l + point = last.add(plane.getGlobalCoords(Vector(l,l,l))) + elif (target.constrain == 1): + dv = fcvec.project(dvec,plane.v) + point = last.add(dv) + if sym: + l = dv.Length + if dv.getAngle(plane.u) > 1: + l = -l + point = last.add(plane.getGlobalCoords(Vector(l,l,l))) + elif (target.constrain == 2): + dv = fcvec.project(dvec,plane.axis) + point = last.add(dv) + if sym: + l = dv.Length + if dv.getAngle(plane.u) > 1: + l = -l + point = last.add(plane.getGlobalCoords(Vector(l,l,l))) + return point diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 0474cb30f..674d07d31 100755 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -34,29 +34,16 @@ from functools import partial from draftlibs import fcvec,fcgeo from FreeCAD import Vector from DraftGui import todo,QtCore,QtGui +from DraftSnap import * +from DraftTrackers import * from pivy import coin -# loads a translation engine -#locale = QtCore.QLocale(eval("QtCore.QLocale."+FreeCADGui.getLocale())).name() -#translator = QtCore.QTranslator() -#translator.load('Draft_'+locale+'.qm',':/translations/') -#QtGui.QApplication.installTranslator(translator) -FreeCADGui.updateLocale() +#--------------------------------------------------------------------------- +# Preflight stuff +#--------------------------------------------------------------------------- -def translate(context,text): - "convenience function for Qt translator" - return QtGui.QApplication.translate(context, text, None, QtGui.QApplication.UnicodeUTF8).toUtf8() - -def msg(text=None,mode=None): - "prints the given message on the FreeCAD status bar" - if not text: FreeCAD.Console.PrintMessage("") - else: - if mode == 'warning': - FreeCAD.Console.PrintWarning(text) - elif mode == 'error': - FreeCAD.Console.PrintError(text) - else: - FreeCAD.Console.PrintMessage(text) +# update the translation engine +FreeCADGui.updateLocale() # loads the fill patterns FreeCAD.svgpatterns = importSVG.getContents(Draft_rc.qt_resource_data,'pattern',True) @@ -85,312 +72,24 @@ MODSNAP = MODS[Draft.getParam("modsnap")] MODALT = MODS[Draft.getParam("modalt")] #--------------------------------------------------------------------------- -# Snapping stuff +# General functions #--------------------------------------------------------------------------- -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: - 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 +def translate(context,text): + "convenience function for Qt translator" + return QtGui.QApplication.translate(context, text, None, QtGui.QApplication.UnicodeUTF8).toUtf8() + +def msg(text=None,mode=None): + "prints the given message on the FreeCAD status bar" + if not text: FreeCAD.Console.PrintMessage("") 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)] + if mode == 'warning': + FreeCAD.Console.PrintWarning(text) + elif mode == 'error': + FreeCAD.Console.PrintError(text) 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 = plane.getClosestAxis(dvec) - if ((target.constrain == None) or mobile): - if affinity == "x": - dv = fcvec.project(dvec,plane.u) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(plane.u) > 1: - l = -l - point = last.add(plane.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,plane.v) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(plane.v) > 1: - l = -l - point = last.add(plane.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,plane.axis) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(plane.axis) > 1: - l = -l - point = last.add(plane.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,plane.u) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(plane.u) > 1: - l = -l - point = last.add(plane.getGlobalCoords(Vector(l,l,l))) - elif (target.constrain == 1): - dv = fcvec.project(dvec,plane.v) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(plane.u) > 1: - l = -l - point = last.add(plane.getGlobalCoords(Vector(l,l,l))) - elif (target.constrain == 2): - dv = fcvec.project(dvec,plane.axis) - point = last.add(dv) - if sym: - l = dv.Length - if dv.getAngle(plane.u) > 1: - l = -l - point = last.add(plane.getGlobalCoords(Vector(l,l,l))) - return point + FreeCAD.Console.PrintMessage(text) def selectObject(arg): '''this is a scene even handler, to be called from the Draft tools @@ -490,592 +189,6 @@ def setMod(args,mod,state): args["CtrlDown"] = state elif mod == "alt": args["AltDown"] = state - -#--------------------------------------------------------------------------- -# Trackers -#--------------------------------------------------------------------------- - -class Tracker: - "A generic Draft Tracker, to be used by other specific trackers" - def __init__(self,dotted=False,scolor=None,swidth=None,children=[],ontop=False): - self.ontop = ontop - color = coin.SoBaseColor() - color.rgb = scolor or FreeCADGui.draftToolBar.getDefaultColor("ui") - drawstyle = coin.SoDrawStyle() - if swidth: - drawstyle.lineWidth = swidth - if dotted: - drawstyle.style = coin.SoDrawStyle.LINES - drawstyle.lineWeight = 3 - drawstyle.linePattern = 0x0f0f #0xaa - node = coin.SoSeparator() - for c in [drawstyle, color] + children: - node.addChild(c) - self.switch = coin.SoSwitch() # this is the on/off switch - self.switch.addChild(node) - self.switch.whichChild = -1 - self.Visible = False - todo.delay(self._insertSwitch, self.switch) - - def finalize(self): - todo.delay(self._removeSwitch, self.switch) - self.switch = None - - def _insertSwitch(self, switch): - '''insert self.switch into the scene graph. Must not be called - from an event handler (or other scene graph traversal).''' - sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() - if self.ontop: - sg.insertChild(switch,0) - else: - sg.addChild(switch) - - def _removeSwitch(self, switch): - '''remove self.switch from the scene graph. As with _insertSwitch, - must not be called during scene graph traversal).''' - sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() - sg.removeChild(switch) - - def on(self): - self.switch.whichChild = 0 - self.Visible = True - - def off(self): - self.switch.whichChild = -1 - self.Visible = False - -class snapTracker(Tracker): - "A Snap Mark tracker, used by tools that support snapping" - def __init__(self): - color = coin.SoBaseColor() - color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.marker = coin.SoMarkerSet() # this is the marker symbol - self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValue((0,0,0)) - node = coin.SoAnnotation() - node.addChild(self.coords) - node.addChild(color) - node.addChild(self.marker) - Tracker.__init__(self,children=[node]) - - def setMarker(self,style): - if (style == "point"): - self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 - elif (style == "square"): - self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9 - elif (style == "circle"): - self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_LINE_9_9 - -class lineTracker(Tracker): - "A Line tracker, used by the tools that need to draw temporary lines" - def __init__(self,dotted=False,scolor=None,swidth=None): - line = coin.SoLineSet() - line.numVertices.setValue(2) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,2,[[0,0,0],[1,0,0]]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) - - def p1(self,point=None): - "sets or gets the first point of the line" - if point: - self.coords.point.set1Value(0,point.x,point.y,point.z) - else: - return Vector(self.coords.point.getValues()[0].getValue()) - - def p2(self,point=None): - "sets or gets the second point of the line" - if point: - self.coords.point.set1Value(1,point.x,point.y,point.z) - else: - return Vector(self.coords.point.getValues()[-1].getValue()) - - def getLength(self): - "returns the length of the line" - p1 = Vector(self.coords.point.getValues()[0].getValue()) - p2 = Vector(self.coords.point.getValues()[-1].getValue()) - return (p2.sub(p1)).Length - -class rectangleTracker(Tracker): - "A Rectangle tracker, used by the rectangle tool" - def __init__(self,dotted=False,scolor=None,swidth=None): - self.origin = Vector(0,0,0) - line = coin.SoLineSet() - line.numVertices.setValue(5) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,50,[[0,0,0],[2,0,0],[2,2,0],[0,2,0],[0,0,0]]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) - self.u = plane.u - self.v = plane.v - - def setorigin(self,point): - "sets the base point of the rectangle" - self.coords.point.set1Value(0,point.x,point.y,point.z) - self.coords.point.set1Value(4,point.x,point.y,point.z) - self.origin = point - - def update(self,point): - "sets the opposite (diagonal) point of the rectangle" - diagonal = point.sub(self.origin) - inpoint1 = self.origin.add(fcvec.project(diagonal,self.v)) - inpoint2 = self.origin.add(fcvec.project(diagonal,self.u)) - self.coords.point.set1Value(1,inpoint1.x,inpoint1.y,inpoint1.z) - self.coords.point.set1Value(2,point.x,point.y,point.z) - self.coords.point.set1Value(3,inpoint2.x,inpoint2.y,inpoint2.z) - - def setPlane(self,u,v=None): - '''sets given (u,v) vectors as working plane. You can give only u - and v will be deduced automatically given current workplane''' - self.u = u - if v: - self.v = v - else: - norm = plane.u.cross(plane.v) - self.v = self.u.cross(norm) - - def p1(self,point=None): - "sets or gets the base point of the rectangle" - if point: - self.setorigin(point) - else: - return Vector(self.coords.point.getValues()[0].getValue()) - - def p2(self): - "gets the second point (on u axis) of the rectangle" - return Vector(self.coords.point.getValues()[3].getValue()) - - def p3(self,point=None): - "sets or gets the opposite (diagonal) point of the rectangle" - if point: - self.update(point) - else: - return Vector(self.coords.point.getValues()[2].getValue()) - - def p4(self): - "gets the fourth point (on v axis) of the rectangle" - return Vector(self.coords.point.getValues()[1].getValue()) - - def getSize(self): - "returns (length,width) of the rectangle" - p1 = Vector(self.coords.point.getValues()[0].getValue()) - p2 = Vector(self.coords.point.getValues()[2].getValue()) - diag = p2.sub(p1) - return ((fcvec.project(diag,self.u)).Length,(fcvec.project(diag,self.v)).Length) - - def getNormal(self): - "returns the normal of the rectangle" - return (self.u.cross(self.v)).normalize() - -class dimTracker(Tracker): - "A Dimension tracker, used by the dimension tool" - def __init__(self,dotted=False,scolor=None,swidth=None): - line = coin.SoLineSet() - line.numVertices.setValue(4) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,4,[[0,0,0],[0,0,0],[0,0,0],[0,0,0]]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) - self.p1 = self.p2 = self.p3 = None - - def update(self,pts): - if len(pts) == 1: - self.p3 = pts[0] - else: - self.p1 = pts[0] - self.p2 = pts[1] - if len(pts) > 2: - self.p3 = pts[2] - self.calc() - - def calc(self): - if (self.p1 != None) and (self.p2 != None): - points = [fcvec.tup(self.p1,True),fcvec.tup(self.p2,True),\ - fcvec.tup(self.p1,True),fcvec.tup(self.p2,True)] - if self.p3 != None: - p1 = self.p1 - p4 = self.p2 - if fcvec.equals(p1,p4): - proj = None - else: - base = Part.Line(p1,p4).toShape() - proj = fcgeo.findDistance(self.p3,base) - if not proj: - p2 = p1 - p3 = p4 - else: - p2 = p1.add(fcvec.neg(proj)) - p3 = p4.add(fcvec.neg(proj)) - points = [fcvec.tup(p1),fcvec.tup(p2),fcvec.tup(p3),fcvec.tup(p4)] - self.coords.point.setValues(0,4,points) - -class bsplineTracker(Tracker): - "A bspline tracker" - def __init__(self,dotted=False,scolor=None,swidth=None,points = []): - self.bspline = None - self.points = points - self.trans = coin.SoTransform() - self.sep = coin.SoSeparator() - self.recompute() - Tracker.__init__(self,dotted,scolor,swidth,[self.trans,self.sep]) - - def update(self, points): - self.points = points - self.recompute() - - def recompute(self): - if (len(self.points) >= 2): - if self.bspline: self.sep.removeChild(self.bspline) - self.bspline = None - c = Part.BSplineCurve() - # DNC: allows to close the curve by placing ends close to each other - if ( len(self.points) >= 3 ) and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ): - # YVH: Added a try to bypass some hazardous situations - try: - c.interpolate(self.points[:-1], True) - except: - pass - elif self.points: - try: - c.interpolate(self.points, False) - except: - pass - c = c.toShape() - buf=c.writeInventor(2,0.01) - #fp=open("spline.iv","w") - #fp.write(buf) - #fp.close() - ivin = coin.SoInput() - ivin.setBuffer(buf) - ivob = coin.SoDB.readAll(ivin) - # In case reading from buffer failed - if ivob and ivob.getNumChildren() > 1: - self.bspline = ivob.getChild(1).getChild(0) - self.bspline.removeChild(self.bspline.getChild(0)) - self.bspline.removeChild(self.bspline.getChild(0)) - self.sep.addChild(self.bspline) - else: - FreeCAD.Console.PrintWarning("bsplineTracker.recompute() failed to read-in Inventor string\n") - -class arcTracker(Tracker): - "An arc tracker" - def __init__(self,dotted=False,scolor=None,swidth=None,start=0,end=math.pi*2): - self.circle = None - self.startangle = math.degrees(start) - self.endangle = math.degrees(end) - self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) - self.sep = coin.SoSeparator() - self.recompute() - Tracker.__init__(self,dotted,scolor,swidth,[self.trans, self.sep]) - - def setCenter(self,cen): - "sets the center point" - self.trans.translation.setValue([cen.x,cen.y,cen.z]) - - def setRadius(self,rad): - "sets the radius" - self.trans.scaleFactor.setValue([rad,rad,rad]) - - def getRadius(self): - "returns the current radius" - return self.trans.scaleFactor.getValue()[0] - - def setStartAngle(self,ang): - "sets the start angle" - self.startangle = math.degrees(ang) - self.recompute() - - def setEndAngle(self,ang): - "sets the end angle" - self.endangle = math.degrees(ang) - self.recompute() - - def getAngle(self,pt): - "returns the angle of a given vector" - c = self.trans.translation.getValue() - center = Vector(c[0],c[1],c[2]) - base = plane.u - rad = pt.sub(center) - return(fcvec.angle(rad,base,plane.axis)) - - def getAngles(self): - "returns the start and end angles" - return(self.startangle,self.endangle) - - def setStartPoint(self,pt): - "sets the start angle from a point" - self.setStartAngle(-self.getAngle(pt)) - - def setEndPoint(self,pt): - "sets the end angle from a point" - self.setEndAngle(self.getAngle(pt)) - - def setApertureAngle(self,ang): - "sets the end angle by giving the aperture angle" - ap = math.degrees(ang) - self.endangle = self.startangle + ap - self.recompute() - - def recompute(self): - if self.circle: self.sep.removeChild(self.circle) - self.circle = None - if self.endangle < self.startangle: - c = Part.makeCircle(1,Vector(0,0,0),plane.axis,self.endangle,self.startangle) - else: - c = Part.makeCircle(1,Vector(0,0,0),plane.axis,self.startangle,self.endangle) - buf=c.writeInventor(2,0.01) - ivin = coin.SoInput() - ivin.setBuffer(buf) - ivob = coin.SoDB.readAll(ivin) - # In case reading from buffer failed - if ivob and ivob.getNumChildren() > 1: - self.circle = ivob.getChild(1).getChild(0) - self.circle.removeChild(self.circle.getChild(0)) - self.circle.removeChild(self.circle.getChild(0)) - self.sep.addChild(self.circle) - else: - FreeCAD.Console.PrintWarning("arcTracker.recompute() failed to read-in Inventor string\n") - -class ghostTracker(Tracker): - '''A Ghost tracker, that allows to copy whole object representations. - You can pass it an object or a list of objects, or a shape.''' - def __init__(self,sel): - self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) - self.children = [self.trans] - self.ivsep = coin.SoSeparator() - try: - if isinstance(sel,Part.Shape): - ivin = coin.SoInput() - ivin.setBuffer(sel.writeInventor()) - ivob = coin.SoDB.readAll(ivin) - self.ivsep.addChild(ivob.getChildren()[1]) - else: - if not isinstance(sel,list): - sel = [sel] - for obj in sel: - self.ivsep.addChild(obj.ViewObject.RootNode.copy()) - except: - print "draft: Couldn't create ghost" - self.children.append(self.ivsep) - Tracker.__init__(self,children=self.children) - - def update(self,obj): - obj.ViewObject.show() - self.finalize() - self.ivsep = coin.SoSeparator() - self.ivsep.addChild(obj.ViewObject.RootNode.copy()) - Tracker.__init__(self,children=[self.ivsep]) - self.on() - obj.ViewObject.hide() - -class editTracker(Tracker): - "A node edit tracker" - def __init__(self,pos=Vector(0,0,0),name="None",idx=0,objcol=None): - color = coin.SoBaseColor() - if objcol: - color.rgb = objcol[:3] - else: - color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.marker = coin.SoMarkerSet() # this is the marker symbol - self.marker.markerIndex = coin.SoMarkerSet.SQUARE_FILLED_9_9 - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValue((pos.x,pos.y,pos.z)) - selnode = coin.SoType.fromName("SoFCSelection").createInstance() - selnode.documentName.setValue(FreeCAD.ActiveDocument.Name) - selnode.objectName.setValue(name) - selnode.subElementName.setValue("EditNode"+str(idx)) - node = coin.SoAnnotation() - selnode.addChild(self.coords) - selnode.addChild(color) - selnode.addChild(self.marker) - node.addChild(selnode) - Tracker.__init__(self,children=[node],ontop=True) - self.on() - - def set(self,pos): - self.coords.point.setValue((pos.x,pos.y,pos.z)) - - def get(self): - p = self.coords.point.getValues()[0] - return Vector(p[0],p[1],p[2]) - - def move(self,delta): - self.set(self.get().add(delta)) - -class PlaneTracker(Tracker): - "A working plane tracker" - def __init__(self): - # getting screen distance - p1 = FreeCADGui.ActiveDocument.ActiveView.getPoint((100,100)) - p2 = FreeCADGui.ActiveDocument.ActiveView.getPoint((110,100)) - bl = (p2.sub(p1)).Length * (Draft.getParam("snapRange")/2) - self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) - m1 = coin.SoMaterial() - m1.transparency.setValue(0.8) - m1.diffuseColor.setValue([0.4,0.4,0.6]) - c1 = coin.SoCoordinate3() - c1.point.setValues([[-bl,-bl,0],[bl,-bl,0],[bl,bl,0],[-bl,bl,0]]) - f = coin.SoIndexedFaceSet() - f.coordIndex.setValues([0,1,2,3]) - m2 = coin.SoMaterial() - m2.transparency.setValue(0.7) - m2.diffuseColor.setValue([0.2,0.2,0.3]) - c2 = coin.SoCoordinate3() - c2.point.setValues([[0,bl,0],[0,0,0],[bl,0,0],[-.05*bl,.95*bl,0],[0,bl,0], - [.05*bl,.95*bl,0],[.95*bl,.05*bl,0],[bl,0,0],[.95*bl,-.05*bl,0]]) - l = coin.SoLineSet() - l.numVertices.setValues([3,3,3]) - s = coin.SoSeparator() - s.addChild(self.trans) - s.addChild(m1) - s.addChild(c1) - s.addChild(f) - s.addChild(m2) - s.addChild(c2) - s.addChild(l) - Tracker.__init__(self,children=[s]) - - def set(self,pos=None): - if pos: - Q = plane.getRotation().Rotation.Q - else: - plm = plane.getPlacement() - Q = plm.Rotation.Q - pos = plm.Base - self.trans.translation.setValue([pos.x,pos.y,pos.z]) - self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) - self.on() - -class wireTracker(Tracker): - "A wire tracker" - def __init__(self,wire): - self.line = coin.SoLineSet() - self.closed = fcgeo.isReallyClosed(wire) - if self.closed: - self.line.numVertices.setValue(len(wire.Vertexes)+1) - else: - self.line.numVertices.setValue(len(wire.Vertexes)) - self.coords = coin.SoCoordinate3() - self.update(wire) - Tracker.__init__(self,children=[self.coords,self.line]) - - def update(self,wire): - if wire: - self.line.numVertices.setValue(len(wire.Vertexes)) - for i in range(len(wire.Vertexes)): - p=wire.Vertexes[i].Point - self.coords.point.set1Value(i,[p.x,p.y,p.z]) - if self.closed: - t = len(wire.Vertexes) - p = wire.Vertexes[0].Point - self.coords.point.set1Value(t,[p.x,p.y,p.z]) - -class gridTracker(Tracker): - "A grid tracker" - def __init__(self): - # self.space = 1 - self.space = Draft.getParam("gridSpacing") - # self.mainlines = 10 - self.mainlines = Draft.getParam("gridEvery") - self.numlines = 100 - col = [0.2,0.2,0.3] - - self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) - - bound = (self.numlines/2)*self.space - pts = [] - mpts = [] - for i in range(self.numlines+1): - curr = -bound + i*self.space - z = 0 - if i/float(self.mainlines) == i/self.mainlines: - mpts.extend([[-bound,curr,z],[bound,curr,z]]) - mpts.extend([[curr,-bound,z],[curr,bound,z]]) - else: - pts.extend([[-bound,curr,z],[bound,curr,z]]) - pts.extend([[curr,-bound,z],[curr,bound,z]]) - idx = [] - midx = [] - for p in range(0,len(pts),2): - idx.append(2) - for mp in range(0,len(mpts),2): - midx.append(2) - - mat1 = coin.SoMaterial() - mat1.transparency.setValue(0.7) - mat1.diffuseColor.setValue(col) - self.coords1 = coin.SoCoordinate3() - self.coords1.point.setValues(pts) - lines1 = coin.SoLineSet() - lines1.numVertices.setValues(idx) - mat2 = coin.SoMaterial() - mat2.transparency.setValue(0.3) - mat2.diffuseColor.setValue(col) - self.coords2 = coin.SoCoordinate3() - self.coords2.point.setValues(mpts) - lines2 = coin.SoLineSet() - lines2.numVertices.setValues(midx) - s = coin.SoSeparator() - s.addChild(self.trans) - s.addChild(mat1) - s.addChild(self.coords1) - s.addChild(lines1) - s.addChild(mat2) - s.addChild(self.coords2) - s.addChild(lines2) - Tracker.__init__(self,children=[s]) - self.update() - - def update(self): - bound = (self.numlines/2)*self.space - pts = [] - mpts = [] - for i in range(self.numlines+1): - curr = -bound + i*self.space - if i/float(self.mainlines) == i/self.mainlines: - mpts.extend([[-bound,curr,0],[bound,curr,0]]) - mpts.extend([[curr,-bound,0],[curr,bound,0]]) - else: - pts.extend([[-bound,curr,0],[bound,curr,0]]) - pts.extend([[curr,-bound,0],[curr,bound,0]]) - self.coords1.point.setValues(pts) - self.coords2.point.setValues(mpts) - - def setSpacing(self,space): - self.space = space - self.update() - - def setMainlines(self,ml): - self.mainlines = ml - self.update() - - def set(self): - Q = plane.getRotation().Rotation.Q - self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) - self.on() - - def getClosestNode(self,point): - "returns the closest node from the given point" - # get the 2D coords. - point = plane.projectPoint(point) - u = fcvec.project(point,plane.u) - lu = u.Length - if u.getAngle(plane.u) > 1.5: - lu = -lu - v = fcvec.project(point,plane.v) - lv = v.Length - if v.getAngle(plane.v) > 1.5: - lv = -lv - # print "u = ",u," v = ",v - # find nearest grid node - pu = (round(lu/self.space,0))*self.space - pv = (round(lv/self.space,0))*self.space - rot = FreeCAD.Rotation() - rot.Q = self.trans.rotation.getValue().getValue() - return rot.multVec(Vector(pu,pv,0)) - #--------------------------------------------------------------------------- # Helper tools diff --git a/src/Mod/Draft/DraftTrackers.py b/src/Mod/Draft/DraftTrackers.py new file mode 100644 index 000000000..b0c48c833 --- /dev/null +++ b/src/Mod/Draft/DraftTrackers.py @@ -0,0 +1,611 @@ +#*************************************************************************** +#* * +#* Copyright (c) 2011 * +#* Yorik van Havre * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU General Public License (GPL) * +#* as published by the Free Software Foundation; either version 2 of * +#* the License, or (at your option) any later version. * +#* for detail see the LICENCE text file. * +#* * +#* This program is distributed in the hope that it will be useful, * +#* but WITHOUT ANY WARRANTY; without even the implied warranty of * +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +#* GNU Library General Public License for more details. * +#* * +#* You should have received a copy of the GNU Library General Public * +#* License along with this program; if not, write to the Free Software * +#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +#* USA * +#* * +#*************************************************************************** + +__title__="FreeCAD Draft Trackers" +__author__ = "Yorik van Havre" +__url__ = "http://free-cad.sourceforge.net" + + +from pivy import coin +from DraftGui import todo + +class Tracker: + "A generic Draft Tracker, to be used by other specific trackers" + def __init__(self,dotted=False,scolor=None,swidth=None,children=[],ontop=False): + self.ontop = ontop + color = coin.SoBaseColor() + color.rgb = scolor or FreeCADGui.draftToolBar.getDefaultColor("ui") + drawstyle = coin.SoDrawStyle() + if swidth: + drawstyle.lineWidth = swidth + if dotted: + drawstyle.style = coin.SoDrawStyle.LINES + drawstyle.lineWeight = 3 + drawstyle.linePattern = 0x0f0f #0xaa + node = coin.SoSeparator() + for c in [drawstyle, color] + children: + node.addChild(c) + self.switch = coin.SoSwitch() # this is the on/off switch + self.switch.addChild(node) + self.switch.whichChild = -1 + self.Visible = False + todo.delay(self._insertSwitch, self.switch) + + def finalize(self): + todo.delay(self._removeSwitch, self.switch) + self.switch = None + + def _insertSwitch(self, switch): + '''insert self.switch into the scene graph. Must not be called + from an event handler (or other scene graph traversal).''' + sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() + if self.ontop: + sg.insertChild(switch,0) + else: + sg.addChild(switch) + + def _removeSwitch(self, switch): + '''remove self.switch from the scene graph. As with _insertSwitch, + must not be called during scene graph traversal).''' + sg=FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() + sg.removeChild(switch) + + def on(self): + self.switch.whichChild = 0 + self.Visible = True + + def off(self): + self.switch.whichChild = -1 + self.Visible = False + +class snapTracker(Tracker): + "A Snap Mark tracker, used by tools that support snapping" + def __init__(self): + color = coin.SoBaseColor() + color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") + self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValue((0,0,0)) + node = coin.SoAnnotation() + node.addChild(self.coords) + node.addChild(color) + node.addChild(self.marker) + Tracker.__init__(self,children=[node]) + + def setMarker(self,style): + if (style == "point"): + self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9 + elif (style == "square"): + self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9 + elif (style == "circle"): + self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_LINE_9_9 + +class lineTracker(Tracker): + "A Line tracker, used by the tools that need to draw temporary lines" + def __init__(self,dotted=False,scolor=None,swidth=None): + line = coin.SoLineSet() + line.numVertices.setValue(2) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0,2,[[0,0,0],[1,0,0]]) + Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) + + def p1(self,point=None): + "sets or gets the first point of the line" + if point: + self.coords.point.set1Value(0,point.x,point.y,point.z) + else: + return Vector(self.coords.point.getValues()[0].getValue()) + + def p2(self,point=None): + "sets or gets the second point of the line" + if point: + self.coords.point.set1Value(1,point.x,point.y,point.z) + else: + return Vector(self.coords.point.getValues()[-1].getValue()) + + def getLength(self): + "returns the length of the line" + p1 = Vector(self.coords.point.getValues()[0].getValue()) + p2 = Vector(self.coords.point.getValues()[-1].getValue()) + return (p2.sub(p1)).Length + +class rectangleTracker(Tracker): + "A Rectangle tracker, used by the rectangle tool" + def __init__(self,dotted=False,scolor=None,swidth=None): + self.origin = Vector(0,0,0) + line = coin.SoLineSet() + line.numVertices.setValue(5) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0,50,[[0,0,0],[2,0,0],[2,2,0],[0,2,0],[0,0,0]]) + Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) + self.u = plane.u + self.v = plane.v + + def setorigin(self,point): + "sets the base point of the rectangle" + self.coords.point.set1Value(0,point.x,point.y,point.z) + self.coords.point.set1Value(4,point.x,point.y,point.z) + self.origin = point + + def update(self,point): + "sets the opposite (diagonal) point of the rectangle" + diagonal = point.sub(self.origin) + inpoint1 = self.origin.add(fcvec.project(diagonal,self.v)) + inpoint2 = self.origin.add(fcvec.project(diagonal,self.u)) + self.coords.point.set1Value(1,inpoint1.x,inpoint1.y,inpoint1.z) + self.coords.point.set1Value(2,point.x,point.y,point.z) + self.coords.point.set1Value(3,inpoint2.x,inpoint2.y,inpoint2.z) + + def setPlane(self,u,v=None): + '''sets given (u,v) vectors as working plane. You can give only u + and v will be deduced automatically given current workplane''' + self.u = u + if v: + self.v = v + else: + norm = plane.u.cross(plane.v) + self.v = self.u.cross(norm) + + def p1(self,point=None): + "sets or gets the base point of the rectangle" + if point: + self.setorigin(point) + else: + return Vector(self.coords.point.getValues()[0].getValue()) + + def p2(self): + "gets the second point (on u axis) of the rectangle" + return Vector(self.coords.point.getValues()[3].getValue()) + + def p3(self,point=None): + "sets or gets the opposite (diagonal) point of the rectangle" + if point: + self.update(point) + else: + return Vector(self.coords.point.getValues()[2].getValue()) + + def p4(self): + "gets the fourth point (on v axis) of the rectangle" + return Vector(self.coords.point.getValues()[1].getValue()) + + def getSize(self): + "returns (length,width) of the rectangle" + p1 = Vector(self.coords.point.getValues()[0].getValue()) + p2 = Vector(self.coords.point.getValues()[2].getValue()) + diag = p2.sub(p1) + return ((fcvec.project(diag,self.u)).Length,(fcvec.project(diag,self.v)).Length) + + def getNormal(self): + "returns the normal of the rectangle" + return (self.u.cross(self.v)).normalize() + +class dimTracker(Tracker): + "A Dimension tracker, used by the dimension tool" + def __init__(self,dotted=False,scolor=None,swidth=None): + line = coin.SoLineSet() + line.numVertices.setValue(4) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0,4,[[0,0,0],[0,0,0],[0,0,0],[0,0,0]]) + Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line]) + self.p1 = self.p2 = self.p3 = None + + def update(self,pts): + if len(pts) == 1: + self.p3 = pts[0] + else: + self.p1 = pts[0] + self.p2 = pts[1] + if len(pts) > 2: + self.p3 = pts[2] + self.calc() + + def calc(self): + if (self.p1 != None) and (self.p2 != None): + points = [fcvec.tup(self.p1,True),fcvec.tup(self.p2,True),\ + fcvec.tup(self.p1,True),fcvec.tup(self.p2,True)] + if self.p3 != None: + p1 = self.p1 + p4 = self.p2 + if fcvec.equals(p1,p4): + proj = None + else: + base = Part.Line(p1,p4).toShape() + proj = fcgeo.findDistance(self.p3,base) + if not proj: + p2 = p1 + p3 = p4 + else: + p2 = p1.add(fcvec.neg(proj)) + p3 = p4.add(fcvec.neg(proj)) + points = [fcvec.tup(p1),fcvec.tup(p2),fcvec.tup(p3),fcvec.tup(p4)] + self.coords.point.setValues(0,4,points) + +class bsplineTracker(Tracker): + "A bspline tracker" + def __init__(self,dotted=False,scolor=None,swidth=None,points = []): + self.bspline = None + self.points = points + self.trans = coin.SoTransform() + self.sep = coin.SoSeparator() + self.recompute() + Tracker.__init__(self,dotted,scolor,swidth,[self.trans,self.sep]) + + def update(self, points): + self.points = points + self.recompute() + + def recompute(self): + if (len(self.points) >= 2): + if self.bspline: self.sep.removeChild(self.bspline) + self.bspline = None + c = Part.BSplineCurve() + # DNC: allows to close the curve by placing ends close to each other + if ( len(self.points) >= 3 ) and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ): + # YVH: Added a try to bypass some hazardous situations + try: + c.interpolate(self.points[:-1], True) + except: + pass + elif self.points: + try: + c.interpolate(self.points, False) + except: + pass + c = c.toShape() + buf=c.writeInventor(2,0.01) + #fp=open("spline.iv","w") + #fp.write(buf) + #fp.close() + ivin = coin.SoInput() + ivin.setBuffer(buf) + ivob = coin.SoDB.readAll(ivin) + # In case reading from buffer failed + if ivob and ivob.getNumChildren() > 1: + self.bspline = ivob.getChild(1).getChild(0) + self.bspline.removeChild(self.bspline.getChild(0)) + self.bspline.removeChild(self.bspline.getChild(0)) + self.sep.addChild(self.bspline) + else: + FreeCAD.Console.PrintWarning("bsplineTracker.recompute() failed to read-in Inventor string\n") + +class arcTracker(Tracker): + "An arc tracker" + def __init__(self,dotted=False,scolor=None,swidth=None,start=0,end=math.pi*2): + self.circle = None + self.startangle = math.degrees(start) + self.endangle = math.degrees(end) + self.trans = coin.SoTransform() + self.trans.translation.setValue([0,0,0]) + self.sep = coin.SoSeparator() + self.recompute() + Tracker.__init__(self,dotted,scolor,swidth,[self.trans, self.sep]) + + def setCenter(self,cen): + "sets the center point" + self.trans.translation.setValue([cen.x,cen.y,cen.z]) + + def setRadius(self,rad): + "sets the radius" + self.trans.scaleFactor.setValue([rad,rad,rad]) + + def getRadius(self): + "returns the current radius" + return self.trans.scaleFactor.getValue()[0] + + def setStartAngle(self,ang): + "sets the start angle" + self.startangle = math.degrees(ang) + self.recompute() + + def setEndAngle(self,ang): + "sets the end angle" + self.endangle = math.degrees(ang) + self.recompute() + + def getAngle(self,pt): + "returns the angle of a given vector" + c = self.trans.translation.getValue() + center = Vector(c[0],c[1],c[2]) + base = plane.u + rad = pt.sub(center) + return(fcvec.angle(rad,base,plane.axis)) + + def getAngles(self): + "returns the start and end angles" + return(self.startangle,self.endangle) + + def setStartPoint(self,pt): + "sets the start angle from a point" + self.setStartAngle(-self.getAngle(pt)) + + def setEndPoint(self,pt): + "sets the end angle from a point" + self.setEndAngle(self.getAngle(pt)) + + def setApertureAngle(self,ang): + "sets the end angle by giving the aperture angle" + ap = math.degrees(ang) + self.endangle = self.startangle + ap + self.recompute() + + def recompute(self): + if self.circle: self.sep.removeChild(self.circle) + self.circle = None + if self.endangle < self.startangle: + c = Part.makeCircle(1,Vector(0,0,0),plane.axis,self.endangle,self.startangle) + else: + c = Part.makeCircle(1,Vector(0,0,0),plane.axis,self.startangle,self.endangle) + buf=c.writeInventor(2,0.01) + ivin = coin.SoInput() + ivin.setBuffer(buf) + ivob = coin.SoDB.readAll(ivin) + # In case reading from buffer failed + if ivob and ivob.getNumChildren() > 1: + self.circle = ivob.getChild(1).getChild(0) + self.circle.removeChild(self.circle.getChild(0)) + self.circle.removeChild(self.circle.getChild(0)) + self.sep.addChild(self.circle) + else: + FreeCAD.Console.PrintWarning("arcTracker.recompute() failed to read-in Inventor string\n") + +class ghostTracker(Tracker): + '''A Ghost tracker, that allows to copy whole object representations. + You can pass it an object or a list of objects, or a shape.''' + def __init__(self,sel): + self.trans = coin.SoTransform() + self.trans.translation.setValue([0,0,0]) + self.children = [self.trans] + self.ivsep = coin.SoSeparator() + try: + if isinstance(sel,Part.Shape): + ivin = coin.SoInput() + ivin.setBuffer(sel.writeInventor()) + ivob = coin.SoDB.readAll(ivin) + self.ivsep.addChild(ivob.getChildren()[1]) + else: + if not isinstance(sel,list): + sel = [sel] + for obj in sel: + self.ivsep.addChild(obj.ViewObject.RootNode.copy()) + except: + print "draft: Couldn't create ghost" + self.children.append(self.ivsep) + Tracker.__init__(self,children=self.children) + + def update(self,obj): + obj.ViewObject.show() + self.finalize() + self.ivsep = coin.SoSeparator() + self.ivsep.addChild(obj.ViewObject.RootNode.copy()) + Tracker.__init__(self,children=[self.ivsep]) + self.on() + obj.ViewObject.hide() + +class editTracker(Tracker): + "A node edit tracker" + def __init__(self,pos=Vector(0,0,0),name="None",idx=0,objcol=None): + color = coin.SoBaseColor() + if objcol: + color.rgb = objcol[:3] + else: + color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") + self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker.markerIndex = coin.SoMarkerSet.SQUARE_FILLED_9_9 + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValue((pos.x,pos.y,pos.z)) + selnode = coin.SoType.fromName("SoFCSelection").createInstance() + selnode.documentName.setValue(FreeCAD.ActiveDocument.Name) + selnode.objectName.setValue(name) + selnode.subElementName.setValue("EditNode"+str(idx)) + node = coin.SoAnnotation() + selnode.addChild(self.coords) + selnode.addChild(color) + selnode.addChild(self.marker) + node.addChild(selnode) + Tracker.__init__(self,children=[node],ontop=True) + self.on() + + def set(self,pos): + self.coords.point.setValue((pos.x,pos.y,pos.z)) + + def get(self): + p = self.coords.point.getValues()[0] + return Vector(p[0],p[1],p[2]) + + def move(self,delta): + self.set(self.get().add(delta)) + +class PlaneTracker(Tracker): + "A working plane tracker" + def __init__(self): + # getting screen distance + p1 = FreeCADGui.ActiveDocument.ActiveView.getPoint((100,100)) + p2 = FreeCADGui.ActiveDocument.ActiveView.getPoint((110,100)) + bl = (p2.sub(p1)).Length * (Draft.getParam("snapRange")/2) + self.trans = coin.SoTransform() + self.trans.translation.setValue([0,0,0]) + m1 = coin.SoMaterial() + m1.transparency.setValue(0.8) + m1.diffuseColor.setValue([0.4,0.4,0.6]) + c1 = coin.SoCoordinate3() + c1.point.setValues([[-bl,-bl,0],[bl,-bl,0],[bl,bl,0],[-bl,bl,0]]) + f = coin.SoIndexedFaceSet() + f.coordIndex.setValues([0,1,2,3]) + m2 = coin.SoMaterial() + m2.transparency.setValue(0.7) + m2.diffuseColor.setValue([0.2,0.2,0.3]) + c2 = coin.SoCoordinate3() + c2.point.setValues([[0,bl,0],[0,0,0],[bl,0,0],[-.05*bl,.95*bl,0],[0,bl,0], + [.05*bl,.95*bl,0],[.95*bl,.05*bl,0],[bl,0,0],[.95*bl,-.05*bl,0]]) + l = coin.SoLineSet() + l.numVertices.setValues([3,3,3]) + s = coin.SoSeparator() + s.addChild(self.trans) + s.addChild(m1) + s.addChild(c1) + s.addChild(f) + s.addChild(m2) + s.addChild(c2) + s.addChild(l) + Tracker.__init__(self,children=[s]) + + def set(self,pos=None): + if pos: + Q = plane.getRotation().Rotation.Q + else: + plm = plane.getPlacement() + Q = plm.Rotation.Q + pos = plm.Base + self.trans.translation.setValue([pos.x,pos.y,pos.z]) + self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) + self.on() + +class wireTracker(Tracker): + "A wire tracker" + def __init__(self,wire): + self.line = coin.SoLineSet() + self.closed = fcgeo.isReallyClosed(wire) + if self.closed: + self.line.numVertices.setValue(len(wire.Vertexes)+1) + else: + self.line.numVertices.setValue(len(wire.Vertexes)) + self.coords = coin.SoCoordinate3() + self.update(wire) + Tracker.__init__(self,children=[self.coords,self.line]) + + def update(self,wire): + if wire: + self.line.numVertices.setValue(len(wire.Vertexes)) + for i in range(len(wire.Vertexes)): + p=wire.Vertexes[i].Point + self.coords.point.set1Value(i,[p.x,p.y,p.z]) + if self.closed: + t = len(wire.Vertexes) + p = wire.Vertexes[0].Point + self.coords.point.set1Value(t,[p.x,p.y,p.z]) + +class gridTracker(Tracker): + "A grid tracker" + def __init__(self): + # self.space = 1 + self.space = Draft.getParam("gridSpacing") + # self.mainlines = 10 + self.mainlines = Draft.getParam("gridEvery") + self.numlines = 100 + col = [0.2,0.2,0.3] + + self.trans = coin.SoTransform() + self.trans.translation.setValue([0,0,0]) + + bound = (self.numlines/2)*self.space + pts = [] + mpts = [] + for i in range(self.numlines+1): + curr = -bound + i*self.space + z = 0 + if i/float(self.mainlines) == i/self.mainlines: + mpts.extend([[-bound,curr,z],[bound,curr,z]]) + mpts.extend([[curr,-bound,z],[curr,bound,z]]) + else: + pts.extend([[-bound,curr,z],[bound,curr,z]]) + pts.extend([[curr,-bound,z],[curr,bound,z]]) + idx = [] + midx = [] + for p in range(0,len(pts),2): + idx.append(2) + for mp in range(0,len(mpts),2): + midx.append(2) + + mat1 = coin.SoMaterial() + mat1.transparency.setValue(0.7) + mat1.diffuseColor.setValue(col) + self.coords1 = coin.SoCoordinate3() + self.coords1.point.setValues(pts) + lines1 = coin.SoLineSet() + lines1.numVertices.setValues(idx) + mat2 = coin.SoMaterial() + mat2.transparency.setValue(0.3) + mat2.diffuseColor.setValue(col) + self.coords2 = coin.SoCoordinate3() + self.coords2.point.setValues(mpts) + lines2 = coin.SoLineSet() + lines2.numVertices.setValues(midx) + s = coin.SoSeparator() + s.addChild(self.trans) + s.addChild(mat1) + s.addChild(self.coords1) + s.addChild(lines1) + s.addChild(mat2) + s.addChild(self.coords2) + s.addChild(lines2) + Tracker.__init__(self,children=[s]) + self.update() + + def update(self): + bound = (self.numlines/2)*self.space + pts = [] + mpts = [] + for i in range(self.numlines+1): + curr = -bound + i*self.space + if i/float(self.mainlines) == i/self.mainlines: + mpts.extend([[-bound,curr,0],[bound,curr,0]]) + mpts.extend([[curr,-bound,0],[curr,bound,0]]) + else: + pts.extend([[-bound,curr,0],[bound,curr,0]]) + pts.extend([[curr,-bound,0],[curr,bound,0]]) + self.coords1.point.setValues(pts) + self.coords2.point.setValues(mpts) + + def setSpacing(self,space): + self.space = space + self.update() + + def setMainlines(self,ml): + self.mainlines = ml + self.update() + + def set(self): + Q = plane.getRotation().Rotation.Q + self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) + self.on() + + def getClosestNode(self,point): + "returns the closest node from the given point" + # get the 2D coords. + point = plane.projectPoint(point) + u = fcvec.project(point,plane.u) + lu = u.Length + if u.getAngle(plane.u) > 1.5: + lu = -lu + v = fcvec.project(point,plane.v) + lv = v.Length + if v.getAngle(plane.v) > 1.5: + lv = -lv + # print "u = ",u," v = ",v + # find nearest grid node + pu = (round(lu/self.space,0))*self.space + pv = (round(lv/self.space,0))*self.space + rot = FreeCAD.Rotation() + rot.Q = self.trans.rotation.getValue().getValue() + return rot.multVec(Vector(pu,pv,0)) diff --git a/src/Mod/Draft/Makefile.am b/src/Mod/Draft/Makefile.am index 2ed301f93..f9b5a99cd 100644 --- a/src/Mod/Draft/Makefile.am +++ b/src/Mod/Draft/Makefile.am @@ -11,6 +11,8 @@ data_DATA = \ Draft.py \ DraftTools.py \ DraftGui.py \ + DraftSnap.py \ + DraftTrackers.py \ WorkingPlane.py \ importOCA.py \ importDXF.py \ diff --git a/src/WindowsInstaller/ModDraft.wxi b/src/WindowsInstaller/ModDraft.wxi index 0f709ef40..13fcc92ff 100644 --- a/src/WindowsInstaller/ModDraft.wxi +++ b/src/WindowsInstaller/ModDraft.wxi @@ -29,6 +29,8 @@ + +